문제 상황
이번에 서버 이전을 하면서 레거시 코드를 고치게 되었는데, 도메인 및 기타 하위 경로가 변경되었습니다. 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는 괜찮았던 것입니다.
결론
자주 사용하는 라이브러리에서 또 새로운 점을 알게 되어서 재밌었습니다.
'질문 정리' 카테고리의 다른 글
HTTP FAILED: java.net.SocketTimeoutException: timeout 문제 해결법 (0) | 2025.04.24 |
---|---|
안드로이드 앱의 기본 빌드 타입을 Release로 변경하는 방법 (0) | 2025.04.22 |
Firebase Cloud Messageing 수신 기능 구현(Compose) (3) | 2024.08.28 |
안드로이드 앱 난독화 알아보기 (2) | 2024.07.14 |
[Dagger/MissingBinding] UserData cannot be provided without an @Provides-annotated method. (0) | 2024.07.13 |