질문 정리

baseUrl 설정 과정에서 일부 경로가 호출 URL에 반영되지 않는 문제

five2week 2025. 4. 18. 17:13

문제 상황

이번에 서버 이전을 하면서 레거시 코드를 고치게 되었는데, 도메인 및 기타 하위 경로가 변경되었습니다. baseUrl을 변경한 후, 일부 경로가 호출 URL에 반영되지 않는 문제가 있어서, 정리해봤습니다.

원래 baseUrl에는 도메인 주소까지만 작성되어있었습니다. 하지만 한 앱에서 하위 경로도 모두 동일하게 사용하는 부분이 있어서 해당 부분까지 포함해서 BaseUrl에 넣어뒀습니다.

예를 들면 https://like.while.android/api/v1/ 와 같은 형식으로 입력했습니다.

baseUrl을 https://like.while.android/api/v1/로 설정했는데도, 실제 API 호출 시 /api/v1/가 생략되었습니다.

이유는 interface의 endpoint path 설정에서 @GET, @POST 등에 슬래시(/)로 시작되는 절대 경로를 사용했기 때문이었습니다.

interface ApiService {
    @GET("/users") // ← 슬래시로 시작 → baseUrl 무시하고 절대 경로
    suspend fun getUsers(): Response<List<User>>
}

이 경우, https://like.while.android/users 로 호출되었습니다.

즉, baseUrl에 포함했던 /api/v1/ 경로가 무시된 채 API가 호출되었습니다.


해결 방법

@GET이나 @POST 등에서 절대 경로가 아닌 상대 경로로 설정해야합니다.

interface ApiService {
    @GET("users") // ← 슬래시 없음 → baseUrl 뒤에 붙음
    suspend fun getUsers(): Response<List<User>>
}

그러면 최종 URL은 이 경우, https://app.nueyne.dev/api/v1/users 로 호출되었습니다.

 

✅ 절대 경로 (Absolute Path)

  • 기준(baseUrl)을 무시하고 지정한 경로 그대로 요청하는 방식
  • 경로 앞에 슬래시(/)가 붙어 있음
  • baseUrl의 경로 부분(/api/v1/ 등)이 무시됨.
@GET("/users")

baseUrl이 https://like.while.android/api/v1/라고 해도, @GET("/users") 처럼 절대 경로를 사용하면 최종 호출 URL은 다음과 같이 됩니다

https://like.while.android/api/v1/
https://like.while.android/users

 

✅ 상대 경로 (Relative Path)

  • 기준(baseUrl) 에 경로가 이어붙는 방식
  • 슬래시(/) 없이 시작하는 게 특징
  • baseUrl을 기준으로 경로가 연결됨
@GET("users")

baseUrl이라면 최종 호출 URL은 이렇게 됩니다.

https://like.while.android/api/v1/
https://like.while.android/api/v1/users

 

정리

  • @GET("/something") → baseUrl의 path는 무시됨
  • @GET("something") → baseUrl 뒤에 붙음 (원하는 동작)

절대 경로와 상대 경로의 개념이었습니다. 대학교 때 파일 처리라는 과목을 수강하면서 배웠던 개념이 여기에 적용되어있었던 것입니다.

해당 프로젝트의 baseUrl은 https://1.123.4.567:1234 와 같은 형식으로 입력되어있고, 그 뒤에 @GET("/users") 로 주소를 붙여서 사용하는 방식으로 동작하고 있던 것이 제 변경 후 호출 URL이 이상해진 근본적인 이유였습니다.

그러나 제가 아는 바에 따르면 baseUrl은 /로 끝나지 않으면 오류가 발생하는 것으로 알고 있었습니다.

하지만 이 프로젝트는 baseUrl을 https://1.123.4.567:1234와 같은 형식으로 사용했고, 문제가 발생하지 않았습니다. 이유는 다음과 같습니다.


✅URL의 기본 구조

https://like.while.android:443/api/v1/users?sort=asc#section2

 

프로토콜 (scheme) 통신 방식 http, https
호스트 (host) 접속할 도메인 이름 또는 IP 주소 like.while.android, 1.123.4.567
포트 (port) 서버와 통신할 때 사용하는 포트 (생략 가능, https는 443) :443, :8000
패스 (path) 서버 내부에서 요청 자원을 찾기 위한 경로 /api/v1/example
쿼리스트링 (query) 파라미터 전달용 ?sort=asc, ?page=1&limit=20
프래그먼트 (fragment) 페이지 내부 특정 위치로 이동 (서버에는 전송되지 않음) #section2

 

✅ 핵심 용어 정리

1. 도메인 (Domain)

  • 사람 친화적인 서버 주소입니다.
  • IP 대신 사용하며, DNS가 이를 IP로 변환해줍니다.
  • 예: google.com, naver.com,like.while.android

2. 호스트 (Host)

  • 네트워크 상의 서버 주소를 의미하며, 도메인이나 IP가 될 수 있어요.
  • like.while.android → 하위 도메인을 포함한 전체 호스트 이름
  • http://1.123.4.567:443 에서도 1.123.4.567이 host
  • Retrofit에서 baseUrl의 호스트는 프로토콜 + 호스트 + 포트까지입니다.

3. 패스 (Path)

  • 서버 안에서 특정 리소스에 접근하기 위한 경로예요.
  • /api/v1/users 같은 것
  • 파일 시스템처럼 계층적 구조를 가짐
  • 예: /api/v1/ 는 API 버전 구분을 위한 경로

 

✅ Retrofit에서 baseUrl의 규칙

Retrofit은 내부적으로 HttpUrl (OkHttp의 클래스)을 사용해 URL을 조립합니다. 이때 다음과 같은 규칙이 적용돼요.

 

baseUrl은 반드시 프로토콜(https://)과 도메인, 그리고 /로 끝나는 path로 설정되어야 합니다.

만약 path가 있다면, 그것도 마지막에 /가 붙어야 합니다.

 

예를 들어, 다음은 정상적인 baseUrl입니다.

https://like.while.android/         
https://like.while.android/api/
https://like.while.android/api/v1/

반면 다음은 오류 발생합니다 (Retrofit 초기화 시 IllegalArgumentException)

https://like.while.android/api

Retrofit은 baseUrl과 @GET 등에서 정의한 endpoint를 단순 문자열로 이어붙이지 않고, OkHttp의 HttpUrl.Builder를 통해 합성합니다. 이때 baseUrl이 /로 끝나지 않으면 마지막 path segment가 "덮어쓰기" 되기 때문에 의도한 경로와 달라질 수 있다고 합니다.

 

✅그런데 왜 https://1.123.4.567:1234 는 괜찮았을까?

https://1.123.4.567:1234 는 path가 없는 URL입니다. 즉, 도메인만 있고 경로(path)가 없기 때문에, Retrofit이 내부적으로 자동으로 /를 붙여서 처리합니다.

https://1.123.4.567:1234

위의 url은 실제로는 다음과 동일하게 해석됩니다

http://1.123.4.567:1234

이건 Retrofit 내부에서 OkHttp가 자동으로 처리해주는 부분입니다.

 

정리2

  • baseUrl은 경로(path)가 있다면 반드시 /로 끝나야 함
  • 경로가 없는 경우 (host:port만 있는 경우), Retrofit이 자동으로 슬래시를 붙여줌
  • 그래서 https://1.123.4.567:1234는 괜찮았던 것입니다.

 

결론

자주 사용하는 라이브러리에서 또 새로운 점을 알게 되어서 재밌었습니다.