반응형

Context

깃헙은 Repository에 Push, Clone 등의 액션을 취할 때 계정을 인증하게끔 되어 있다. public repository라면 설정에 따라 크게 상관없을 수 있으나 private repository 이거나 권한이 엄격히 관리되는 repository라면 인증이 필수적이다.

일반적으로 계정 인증은 계정명/패스워드로 이루어지지만 현재 깃헙에서는 이러한 방식의 인증을 사용할 수 없도록 제한하고 있다.

이를 대체할 수 있는 가장 대표적인 방법이 SSH 인증 방식인데, 많은 사람들이 하나 Mac에서 여러 깃헙 계정을 운영하는 것에 어려움을 겪는 것을 보고 글을 남겨본다. Windows & Linux는 기본적으로 되는 것 같으나 Mac에서는 추가적인 작업이 필요하다.

깃헙 SSH Key 등록

SSH Key를 발급하고 GitHub 계정에 등록하는 과정은 공식 문서(링크)에 자세히 나와있으니 간략하게만 다루겠다. 공식 문서의 과정 중 config 파일 설정은 잠시 생략해도 좋다.

  1. 터미널 실행
  2. GitHub email address를 포함해 아래 명령어 입력
    $ ssh-keygen -t ed25519 -C "your_email@example.com"​
  3. GitHub 계정에 SSH 공개키 등록: 링크

config 파일을 이용한 여러 계정 운용하기

하나의 계정에 대한 ssh 키를 발행하고, GitHub에 공개키 등록까지 마쳤다면, 이제 ssh config 파일을 작성해야 한다.

$ vi ~/.ssh/config

이 곳에 아래와 같은 형태로 작성해주면 된다.

Host github.com
    IdentityFile ~/.ssh/{앞서 생성한 SSH 키 이름}
    HostName github.com
    User {GitHub 사용자명}
    
    
[작성 예시]
Host github.com
    IdentityFile ~/.ssh/github_960813
    HostName github.com
    User 960813

여기까지 완료되었다면 SSH 연동이 완료된 것이다.

이제 Repository를 클론해보자. 일반적인 HTTPS 방식이 아니라 SSH 방식을 사용해야 한다. GitHub Web에서는 Code 버튼을 눌러 URL을 찾을 수 있다.

$ git clone git@github.com:ev-incentive-board/system.git

정상적으로 클론 되었을 것이다.

우리는 여기서 Repository의 URL을 잘 살펴보아야 한다. URL은 크게 3가지 영역으로 구분되는데 이는 다음과 같다.

  • git: SSH 사용자가 git 임을 명시
  • @github.com: SSH를 이용해 액세스하려는 Host
  • ev-incentive-board/system.git: Repository 위치

@github.com 이 핵심이다. 이는 앞서 작성한 config 파일의 Host에 해당하는 부분이다. 즉, 여러 인증서를 사용하려면 이 부분을 적절히 바꿔 원하는 IdentifyFile을 사용하도록하면 된다.

Host github-960813
    HostName github.com
    IdentityFile ~/.ssh/github_960813
    User 960813

Host github-xxxxxxx
    IdentityFile ~/.ssh/github_xxxxxxx
    HostName github.com
    User xxxxxxx

config 파일을 위와같이 설정했다면, repository의 remote-url을 git@github-960813:..... / git@github-xxxxxxx:.... 꼴로 사용하면 정상 동작하는 것을 확인할 수 있을 것이다.

이미 local에서 사용하고 있는 repository라면 아래 명령어를 이용하자.

$ git remote set-url origin git@github-xxxxxx:....
반응형

'Git' 카테고리의 다른 글

여러 깃헙 계정을 SSH 방식으로 사용하는 방법  (0) 2021.09.12
잘못된 내용이 있다면, 다른분들에게 전달되지 않게끔 댓글로 지적 부탁드립니다!
반응형

Context

DI에 대한 이야기를 하기 전, 의존성 그 자체에 대한 이야기를 먼저 해보자.

의존성은 무엇이고, 언제 발생하는가?

  • 바리스타가 커피를 만들기 위해선 커피 머신의 협력이 필요하다. 즉 바리스타의 '커피 만들기' 라는 행위는 커피 머신에 의존적이다.
  • 어떠한 애플리케이션은 작업을 처리하기 앞서 설정 값을 참조한다. 이 때 애플리케이션은 설정 값에 의존적이다.

두 번째 예시를 코드로 옮겨보면 다음과 같다. Calculator는 CalculatorConfig의 값에 따라 동작 여부를 결정한다.

data class CalculatorConfig(
    val name: String,
    val status: Boolean = true
)

class Calculator {
    fun isAvailable(): Boolean {
        val config = CalculatorConfig("First Calculator", true)
        return config.status
    }
}

이 코드는 동작하는데 전혀 문제가 없다. 하지만 테스트 코드를 작성하거나 실제로 비즈니스 로직을 개발하다보면 문제가 있음을 쉽게 알 수 있다.

강결합으로 인해 발생하는 문제

위 예시에서 CalculatorConfig는 isAvailable() 함수의 로컬 변수로 선언되어 참조되고 있다. Config의 값이 isAvailable() 함수에 강하게 결합되어 있는 것이다.

특정 함수에 강결합이다 보니 값을 변경하기 위해선 함수 자체를 수정해야 하며, 같은 Calculator 클래스 내의 다른 함수에서도 사용할 수 없게 된다.

그리고 이 상황에서는 테스트 코드를 작성하기도 어렵다. 아래는 Config의 status가 true이면 true를 반환하고, false이면 false를 반환하는지 확인하는 테스트코드이다. 하지만 지금은 이 중 하나는 무조건 실패하게 되어있다.

class DiExampleTest {
    private val calculator = Calculator()

    @Test
    fun `config 의 status 가 false 이면 false 를 리턴한다`() {
        // when
        val actual = calculator.isAvailable()

        // then
        expectThat(actual) isEqualTo false
    }

    @Test
    fun `config 의 status 가 true 이면 true 를 리턴한다`() {
        // when
        val actual = calculator.isAvailable()

        // then
        expectThat(actual) isEqualTo true
    }
}

이 테스트를 통과하려면 Config 인스턴스를 isAvailable() 함수 안에서 만드는 것이 아닌, 외부에서 만들어 전달해주게끔 바꿔야 한다. 외부에서 isAvailable() 함수로 Config 인스턴스를 전달해주는 방법으로는 두 가지가 있다.

  • Calculator 클래스의 생성자로 전달
  • isAvailable() 함수의 인자로 전달

이 중 두 번째 방법으로 리팩토링을 해보겠다.

data class CalculatorConfig(
    val name: String,
    val status: Boolean = true
)

class Calculator {
    fun isAvailable(config: CalculatorConfig): Boolean {
        return config.status
    }
}

// ....

class DiExampleTest {
    private val calculator = Calculator()

    @Test
    fun `config 의 status 가 false 이면 false 를 리턴한다`() {
        // then
        val config = CalculatorConfig("First Calculator", false)

        // when
        val actual = calculator.isAvailable(config)

        // then
        expectThat(actual) isEqualTo false
    }

    @Test
    fun `config 의 status 가 true 이면 true 를 리턴한다`() {
        //then
        val config = CalculatorConfig("First Calculator", true)

        // when
        val actual = calculator.isAvailable(config)

        // then
        expectThat(actual) isEqualTo true
    }
}

외부에서 Config 인스턴스를 생성해 isAvailable() 함수로 전달해줌으로써 두 테스트 모두 통과할 수 있게 되었다. Config 인스턴스를 생성자로 전달 할지, 함수의 인자로 전달 할지는 의존 관계에 따라 달라지니 코드의 흐름에 따라 적절히 선택하면 된다.

DI의 필요성

이처럼 클래스나 함수에서 사용할 인스턴스의 결정권을 자신이 아닌, 외부에 위임함으로써 조금 더 유연한 구조를 갖게하는 방식을 DI라고 말할 수 있다.

유연한 구조를 가진 애플리케이션은 테스트가 용이하며, 최소한의 수정으로 확장할 수 있음을 의미하기도 한다.

하지만 현재의 방식은 외부에서 인스턴스를 직접 생성하여 생성자나 함수의 인자로 전달해줘야 하는 불편함이 존재한다.

이를 극복하고자 IoC(Inversion Of Control) 개념이 등장했으며, 다음에는 IoC의 개념과 Spring Framework의 IoC 동작 방식에 대한 이야기를 써보려 한다.

반응형

'Spring Framework' 카테고리의 다른 글

DI(Dependency Injection)는 왜 필요한가?  (2) 2021.08.29
잘못된 내용이 있다면, 다른분들에게 전달되지 않게끔 댓글로 지적 부탁드립니다!
  1. 잘읽었습니다 2021.09.03 14:37

    안녕하세요 DI 관련해서 읽어본 것 중에 가장 이해에 도움이 됐습니다.
    쉬운 예로 처음 공부하는 분들에게 많은 도움이 될 것 같네요.
    다음 ioc 포스팅도 기다리고 있겠습니다.

    • Aaron 2021.09.04 12:42

      감사합니다 ! 더 좋은 정보 공유드릴 수 있도록 노력하겠습니다.

반응형

핵클(https://hackle.io)에서 Software Enginner로 재직 중인 Aaron 입니다.

2020년 초에 유행하기 시작한 요놈의 바이러스는 아직도 기승을 부리고 있죠.

그러다 보니 코로나19 전파 초기 감염병 예방 및 감염 전파의 차단을 위해 전자출입명부(QR)와 수기출입명부가 도입되어 사용되고 있는데요.

여러분도 식당이나 카페 같은 다중이용 시설에서 사용해보신 적 있으시죠?

이번 게시글에서는 코드포코리아에서 활동하며 있었던 수기출입명부와 관련된 이야기를 풀어보고자 합니다.

수기출입명부

초기 수기출입명부는 이름과 휴대전화번호, 주소를 적게 되어 있었습니다.

그러나 사생활 침해가 심하다는 이유로 지난해 9월 이름을 제외하고 휴대전화 번호와 주소지 시·군·구까지만 기재하도록 방역수칙이 변경되었습니다.

그럼에도 휴대전화 번호 유출에 따른 개인정보 오·남용 사례는 끊이지 않았습니다.

대표적인 사례로는 수기명부에 휴대전화 번호를 적었다가 모르는 사람한테 연락을 받거나 홍보성 문자메시지(스팸)가 급증하는 등의 문제가 있었습니다.

수기명부 악용 사례(문자 메시지)

더군다나 전국 다중이용시설 3만2여천 개 중

  • 전자출입 명부와 수기명부를 함께 사용하는 곳이 56.3%
  • 수기명부만 사용하는 곳이 42.5%
  • 아예 사용하지 않는 곳은 1.2%

무려 40%가 넘는 업소에서 전화번호를 적어야 하는 상황이었습니다.

정부 개인정보보호위원회는 지난해부터 수기출입명부를 통한 개인정보 유출 문제를 해결하기 위해 고민해왔으나 마땅한 답을 찾지 못하고 있었죠.

개인안심번호

그러던 지난해 12월, ‘코드포코리아(https://codefor.kr)’에 이러한 연락이 오게 됩니다.

코로나19 확산에 대비한 수기입장 방식 개선 방법이 있을까요?

저희는 회의를 통해 총 9개의 아이디어를 정리하여 제공했고, 개인정보보호위원회는 그 중 ‘QR 코드 아래 한글과 숫자 조합의 문자 발급’ 방식에 흥미를 표출했습니다.

그 후 여러 이야기가 오가며 개인 안심번호의 윤곽이 만들어졌고, 처음 아이디어를 낸 저희가 개발도 맡게 됩니다.

실제 개발 과정에서는 몇 가지 주요 논의 사항이 존재했습니다.

  1. 개인 안심번호로 휴대전화번호를 유추할 수 없게끔 해야 한다.
  2. 개인 안심번호는 정부 외에 그 누구도 휴대전화번호로 변환할 수 없게 해야 한다.
  3. 국민들이 사용하기 쉽게, 최대한 짧게 구성해야 한다.

구체적인 내용은 대외비인지라 말씀드리지 못하지만 수많은 논의와 밤샘 끝에 개발이 완료됐습니다.

비하인드 스토리

그런데…

정부의 초기 요구는 ‘스마트폰 사용자 대상’ 이었습니다. 그리고 저희는 011, 016, 017, 018, 019와 같은 전화번호는 스마트폰을 사용하지 않으리라 생각했습니다.

그렇기에 010 전화번호만 지원했었는데.. 내부에서 조금 더 알아보니 010이 아닌 전화번호도 스마트폰을 사용할 수 있다는 것입니다.

저희는 다급히 정부와 통신사에 010이 아닌 전화번호의 체계, 현황 공유를 요청했고 대한민국에 존재하는 모든 전화번호에 대응하는 개인 안심번호를 개발할 수 있었습니다.

이렇게 개발된 개인 안심번호는 공적 마스크 공공데이터 개방/개발(히스토리: https://codefor.kr/모두가_함께_하는_공적마스크_이야기)에 이은 민-관 협력의 대표 사례로 카카오, 네이버, PASS 앱을 통해 발 빠르게 사용자에게 배포될 수 있었습니다.

코로나19 개인안심번호 홍보자료

재능 기부

여기까지 이야기를 들으신 여러분은 이런 생각을 하실 수 있습니다.

결국 돈을 받고 한 것 아닌가?

개인 안심번호 논의 초기 단계에서 개인정보보호위원회는 용역계약을 통해 개발을 진행하자고 했었습니다.

그러나 개발을 함께하기로 한 사람들은 정부와 함께 문제를 해결하려는 마음을 가진 분이 많았습니다.(저희는 이를 시빅해킹이라 부릅니다.)

또한 이 사업은 공익 목적이며, 보다 빠른 실제 적용을 위해 별도의 수입 없이 재능기부로 참여하겠다는 뜻을 전하며 개발을 시작하게 되었습니다.

만약 계약하고 진행했다면 최소 5천만 원에서 최대 억대 규모의 계약이 됐을 것이지만, 그만큼 실제 적용이 늦춰졌을 것입니다.

그리고 현재는 디지털 취약계층이신 노인분들이 개인 안심번호를 발급받으실 수 있는 방법을 강구하고 있습니다.

사회 문제를 해결하기 위해 자신의 노력과 시간을 할애하는 많은 분들과 함께하며(개발자, 디자이너, 기획자, 공무원, 교육자, 심지어 현직 국회의원분도 계십니다), 이 외에도 다양한 에피소드가 있었는데 다음에는 여러 가지 이야기를 더 공유해 드리고자 합니다.

아마 공적 마스크 현황 데이터 공개 요청과 관련된 이야기가 그다음이 되지 않을까 싶습니다.

저희의 활동 그자체인 ‘시빅해킹’에 관한 이야기도 해보고 싶네요.

개인정보 보호 차원에서 널리 홍보되었으면 하는 바람과 함께 이렇게 이야기를 풀어보게 되었습니다.

저희는 현재 후원도 받지 않고 있으며, 광고 수입도 없이 100% 재능 기부로 함께하고 있습니다. 사회 문제 해결에 관심 있으신 분들이 이야기를 나눌 수 있는 공간도 마련되어 있으니 관심 있으신분들은 함께해도 좋을 것 같습니다.

긴 글 읽어주신 여러분, 그리고 사회 문제를 해결하기 위해 항상 노력하시는 모든 분께 감사의 인사를 드립니다.

감사합니다 !

반응형
잘못된 내용이 있다면, 다른분들에게 전달되지 않게끔 댓글로 지적 부탁드립니다!
반응형

JVM Memory는 스택과 힙 영역으로 구분된다. 이들은 어떻게 다르고 멀티 스레드에서는 어떻게 동작하는걸까?

Context

코틀린 프로그래밍에서 클래스의 인스턴스를 생성할 때 비용이 발생한다. 인스턴스를 생성하고 더 이상 사용하지 않을 경우 가비지 콜렉션 과정을 통해 메모리에서 해제하는 과정 또한 비용이 발생한다.따라서 인스턴스를 매번 생성할 필요가 없는 경우 매번 인스턴스를 생성하지 않는 것이 성능 측면에서 더 유리하다.

이 때문에 개발자는 요청마다 매번 인스턴스를 생성해야 하는지, 생성하지 않고 이미 생성된 인스턴스를 재사용할 것인지를 판단해야 한다. 이에 대한 기준은 인스턴스가 상태 값을 유지해야 하는지에 따라 구분된다.

송금 관리 시스템

예를 들어 은행 송금 시스템의 경우엔 송금을 관리하는 하나의 인스턴스를 만들어 놓고 송금 금액, 보내는 사람, 받는 사람 등의 정보만 입력 받아 처리해주면 될 것이다. (썩 좋은 예시는 아니지만 넘어가자)

data class Customer(
    val name: String,
    val accountNumber: String
)

data class Transfer(
    val customer: Customer,
    val quantity: Long
)

object TransferManager {
    lateinit var transfer: Transfer
    lateinit var transferTarget: Customer

    fun transfer() {
        println("Quantity: ${transfer.quantity}, ${transfer.customer} -> $transferTarget")
    }
}

송금 정보(금액, 보내는 사람)와 받는 사람을 필드로 갖는 TransferManager를 한 개 만들었다.

JVM은 코드를 실행하기 위해 메모리를 스택과 힙 영역으로 나눠서 관리한다. 스택 영역은 각 메소드가 실행될 때 메소드의 인자, 로컬 변수 등을 관리하는 메모리 영역으로 각 스레드마다 서로 다른 스택 영역을 가진다. 힙 영역은 클래스의 인스턴스 상태 데이터를 관리하는 영역이다. 힙 영역은 스레드가 서로 공유할 수 있다.

위 예시에서 TransferManager의 transfer와 transferTarget 필드는 JVM Heap에 적재되어 모든 스레드에서 공동으로 접근할 수 있게 된다.

발생할 수 있는 문제

현재 TransferManager는 애플리케이션 상에 단 한개의 인스턴스만 존재한다. 그리고 transfer와 transferTarget이 Heap영역에 적재되어 여러 스레드에서 동일한 transfer, transferTarget을 바라보고 있게 된다.

fun main() {
    // 첫 번째 송금 요청: 송금이 지연 됨
    thread {
        val transferCustomer = Customer(
            name = "Aaron Jin",
            accountNumber = "0000-0000-0000"
        )

        val transferTarget = Customer(
            name = "Martin",
            accountNumber = "1111-1111-1111"
        )

        TransferManager.transfer = Transfer(transferCustomer, 50000)
        TransferManager.transferTarget = transferTarget

        Thread.sleep(1000)

        println("첫 번째 송금 요청")
        TransferManager.transfer()
    }

    // 두 번째 송금 요청: 송금 요청이 바로 처리 됨
    thread {
        val transferCustomer = Customer(
            name = "Wanda Morton",
            accountNumber = "2222-2222-2222"
        )

        val transferTarget = Customer(
            name = "Philip Adams",
            accountNumber = "3333-3333-3333"
        )

        TransferManager.transfer = Transfer(transferCustomer, 1000000)
        TransferManager.transferTarget = transferTarget

        println("두 번째 송금 요청")
        TransferManager.transfer()
    }
}

두 개의 스레드 안에서 각각 송금 요청을 하고 있는 케이스다. (단, 첫 번째 요청은 1초가 소요되게끔 코드를 작성했다.)

  • 첫 번째 요청: Aaron Jin -> Martin (50,000원)
  • 두 번째 요청: Wanda Morton -> Philip Adams (1,000,000원)

이 코드를 실행한다면 어렵지 않게 두 번째 요청이 먼저 처리되고 약 1초 뒤 첫 번째 요청이 처리될 것이라는 것을 추측해볼 수 있다. 그리고 여기서 문제가 발생한다.

<실행 결과>
두 번째 송금 요청
Quantity: 1000000, Customer(name=Wanda Morton, accountNumber=2222-2222-2222) -> Customer(name=Philip Adams, accountNumber=3333-3333-3333)
첫 번째 송금 요청
Quantity: 1000000, Customer(name=Wanda Morton, accountNumber=2222-2222-2222) -> Customer(name=Philip Adams, accountNumber=3333-3333-3333)

첫 번째 송금 요청이 Wanda Morton -> Philip Adams (1,000,000원)으로 처리되었다.

이는 두 요청에서 사용되는 transfer, transferTarget이 힙 영역에 적재되어있기 때문이다. 이러한 문제를 발생시키지 않기 위해선 이러한 데이터를 스택 영역에 적재해야만 한다.

해결

package com.example.blog

import kotlin.concurrent.thread

data class Customer(
    val name: String,
    val accountNumber: String
)

data class Transfer(
    val customer: Customer,
    val quantity: Long
)

object TransferManager {
    fun transfer(transfer: Transfer, transferTarget: Customer) {
        println("Quantity: ${transfer.quantity}, ${transfer.customer} -> $transferTarget")
    }
}

fun main() {
    // 첫 번째 송금 요청: 송금이 지연 됨
    thread {
        val transferCustomer = Customer(
            name = "Aaron Jin",
            accountNumber = "0000-0000-0000"
        )

        val transferTarget = Customer(
            name = "Martin",
            accountNumber = "1111-1111-1111"
        )

        Thread.sleep(1000)

        println("첫 번째 송금 요청")
        TransferManager.transfer(
            transfer = Transfer(transferCustomer, 50000),
            transferTarget = transferTarget
        )
    }

    // 두 번째 송금 요청: 송금 요청이 바로 처리 됨
    thread {
        val transferCustomer = Customer(
            name = "Wanda Morton",
            accountNumber = "2222-2222-2222"
        )

        val transferTarget = Customer(
            name = "Philip Adams",
            accountNumber = "3333-3333-3333"
        )

        println("두 번째 송금 요청")
        TransferManager.transfer(
            transfer = Transfer(transferCustomer, 1000000),
            transferTarget = transferTarget
        )
    }
}
<실행 결과>
두 번째 송금 요청
Quantity: 1000000, Customer(name=Wanda Morton, accountNumber=2222-2222-2222) -> Customer(name=Philip Adams, accountNumber=3333-3333-3333)
첫 번째 송금 요청
Quantity: 50000, Customer(name=Aaron Jin, accountNumber=0000-0000-0000) -> Customer(name=Martin, accountNumber=1111-1111-1111)

 

반응형

'Kotlin&Java' 카테고리의 다른 글

스택과 힙 메모리, 그리고 멀티 스레드  (0) 2021.08.22
잘못된 내용이 있다면, 다른분들에게 전달되지 않게끔 댓글로 지적 부탁드립니다!
반응형

Java를 이용해 웹 서버를 구현하던 중 신기한 현상(?)을 발견해 그 내용과 해결 방법을 공유해보려 한다.

Context

Java에서 서버소켓을 생성하기 위해선 ServerSocket 클래스가 사용된다.

int port = 8080;
try (ServerSocket listenSocket = new ServerSocket(port)) {
    log.info("Web Application Server started {} port.", port);

    // 클라이언트가 연결될때까지 대기한다.
    Socket connection;
    while ((connection = listenSocket.accept()) != null) {
        // Do something ..
    }
}

그리고 해당 서버에 액세스하는 방법은 크게 3가지 존재한다

  1. http://localhost:8080
  2. http://127.0.0.1:8080
  3. http://[::1]:8080

2번은 IPv4, 3번은 IPv6이다. 그럼 1번은 둘 중 어떤걸로 동작하는걸까? 시스템의 기본 값?

확인해보기 위해서는 실제로 요청을 보내볼 필요가 있다. 그리고 조금 더 편하게 요청을 보내기 위해 Postman을 사용했다.

Postman GET Request

그런데 분명 요청을 1번 보냈음에도 서버에는 2번의 요청이 수신되었다. 크롬 브라우저처럼 /favicon.ico에 대한 요청을 보내는 것인가? 라는 생각이 들어 RequestHeader를 파싱하여 확인해보았는데...

첫 번째 요청. RequestHeader가 null이다
두 번째 요청. 내가 보냈던 요청에 대한 RequestHeader이다.

첫 번째는 요청에는 Header가 담겨있지 않았으며, 두 번째 요청은 내가 의도했던 Header가 담겨있었다.

그렇다면  첫 번째 요청은 무엇인가?

의문의 첫 번째 요청

내가 구현한 서버의 문제인지, Postman의 요청인지 알 수 없어 우선 nc로 port를 열고 Postman으로 동일한 요청을 보내봤다. 그런데 정상적으로 잘 동작한다. (내가 서버를 잘못 구현한건가?)

이해가 되지 않아 와이어샤크를 이용해 전송되는 패킷을 확인해보았다.

응? SYN...? IPv6...?

문제의 발견

그렇다. Postman은 localhost로 요청을 보낼 때 IPv6로 SYN을 먼저 보내고 그 후 IPv4로 Connection하는 방식을 사용하는 것이다.

도대체 왜? 그냥 시스템의 기본 설정으로 동작하게끔 해주면 안되나? 검색을 하다보니 나와 비슷한 생각을 가진 사람이 있었다.

https://github.com/pocoproject/poco/issues/2605

 

Default ServerSocket uses IPv4 only, StreamSocket defaults to IPv6 · Issue #2605 · pocoproject/poco

Expected behavior When I create a server (based on TCPServer) and client (using StreamSocket), I expected that they can connect without any issues or further configuration required. Actual behavior...

github.com

ServerSocket 을 이용해 접속을 대기하는데, IPv6 요청이 들어와 Connection Fail이 발생한다는 내용이다.

댓글에는 대부분의 Api Client가 IPv6로 SYN을 보내고 IPv4 Connection 하고 있으며 이를 해결하기 위해선 ServerSocket 생성자에 바인드에 사용할 IP를 명시적으로 작성해주라는 조언이 남겨져 있었다.


아니, 이렇게까지 처리를 해줘야 한다고? 그럼 다른 웹 서버 프레임워크는 어떻게 동작하는거지? 

스프링 프레임워크의 동작을 한번 살펴보자.

아.. 얘네도 명시적으로 IP를 지정해주는구나.. 근데 localhost로 지정해주네? 이건 InetAddress.getByName 메소드가 시스템에서 사용하는 IP 버전을 가져오게끔 설정되어 있기 때문이다. 즉 localhost의 IPv4에 대응되는 127.0.0.1으로 설정되는 것이다.

그래서 나도 내 서버의 구현을 위와 같은 형태로 수정했고, 정상적으로 IPv4에 대응되는 요청만 받아낼 수 있었다.


추측으로는 IPv6로 SYN을 보내 연결이 가능한지 확인한 후 가능하다면 IPv6로 연결하고 그렇지 않다면 IPv4로 연결하는 것 같다.

그래도 확실하지 않고 여전히 의문이기에 관련된 내용을 Postman App Support Issues에 질문으로 작성해두었다. 언젠가 답변이 도착하면 업데이트 해두려 한다.

https://github.com/postmanlabs/postman-app-support/issues/10221

 

When i requested to 'localhost' host, the Postman sends SYN to IPv6, and then connects to IPv4. · Issue #10221 · postmanlabs/p

Is there an existing issue for this? I have searched the existing issues Describe the Issue When I send a requested to 'localhost' host, it attempts to connect without having a Request Head...

github.com

 

반응형

'Web' 카테고리의 다른 글

localhost는 IPv6와 IPv4 어떤걸로 동작할까?  (0) 2021.08.16
잘못된 내용이 있다면, 다른분들에게 전달되지 않게끔 댓글로 지적 부탁드립니다!

+ Recent posts