Skip to content

Commit d4ec79b

Browse files
authored
RCORE-2096 Trim trailing slashes from base urls to prevent 404 errors (#7791)
1 parent b576cbc commit d4ec79b

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Enhancements
44
* <New feature description> (PR [#????](https://github.com/realm/realm-core/pull/????))
5+
* It is no longer an error to set a base url for an App with a trailing slash - for example, `https://services.cloud.mongodb.com/` instead of `https://services.cloud.mongodb.com` - before this change that would result in a 404 error from the server ([PR #7791](https://github.com/realm/realm-core/pull/7791)).
56
* Performance has been improved for range queries on integers and timestamps. Requires that you use the "BETWEEN" operation in MQL or the Query::between() method when you build the query. (PR [#7785](https://github.com/realm/realm-core/pull/7785))
67
* None.
78

src/realm/object-store/sync/app.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,24 @@ HttpHeaders get_request_headers(const std::shared_ptr<User>& user, RequestTokenT
157157
return headers;
158158
}
159159

160+
std::string trim_base_url(std::string base_url)
161+
{
162+
while (!base_url.empty() && base_url.back() == '/') {
163+
base_url.pop_back();
164+
}
165+
166+
return base_url;
167+
}
168+
169+
std::string base_url_from_app_config(const AppConfig& app_config)
170+
{
171+
if (!app_config.base_url) {
172+
return std::string{App::default_base_url()};
173+
}
174+
175+
return trim_base_url(*app_config.base_url);
176+
}
177+
160178
UniqueFunction<void(const Response&)> handle_default_response(UniqueFunction<void(Optional<AppError>)>&& completion)
161179
{
162180
return [completion = std::move(completion)](const Response& response) {
@@ -190,7 +208,7 @@ SharedApp App::get_app(CacheMode mode, const AppConfig& config) NO_THREAD_SAFETY
190208
{
191209
if (mode == CacheMode::Enabled) {
192210
std::lock_guard lock(s_apps_mutex);
193-
auto& app = s_apps_cache[config.app_id][config.base_url.value_or(std::string(App::default_base_url()))];
211+
auto& app = s_apps_cache[config.app_id][base_url_from_app_config(config)];
194212
if (!app) {
195213
app = std::make_shared<App>(Private(), config);
196214
}
@@ -206,7 +224,7 @@ SharedApp App::get_cached_app(const std::string& app_id, const std::optional<std
206224
if (auto it = s_apps_cache.find(app_id); it != s_apps_cache.end()) {
207225
const auto& apps_by_url = it->second;
208226

209-
auto app_it = base_url ? apps_by_url.find(*base_url) : apps_by_url.begin();
227+
auto app_it = base_url ? apps_by_url.find(trim_base_url(*base_url)) : apps_by_url.begin();
210228
if (app_it != apps_by_url.end()) {
211229
return app_it->second;
212230
}
@@ -233,7 +251,7 @@ void App::close_all_sync_sessions()
233251

234252
App::App(Private, const AppConfig& config)
235253
: m_config(config)
236-
, m_base_url(m_config.base_url.value_or(std::string(App::default_base_url())))
254+
, m_base_url(base_url_from_app_config(m_config))
237255
, m_request_timeout_ms(m_config.default_request_timeout_ms.value_or(s_default_timeout_ms))
238256
, m_file_manager(std::make_unique<SyncFileManager>(config))
239257
, m_metadata_store(create_metadata_store(config, *m_file_manager))
@@ -393,7 +411,7 @@ void App::update_hostname(const std::string& host_url, const std::string& ws_hos
393411
const std::string& new_base_url)
394412
{
395413
log_debug("App: update_hostname: %1 | %2 | %3", host_url, ws_host_url, new_base_url);
396-
m_base_url = new_base_url;
414+
m_base_url = trim_base_url(new_base_url);
397415
// If a new host url was returned from the server, use it to configure the routes
398416
// Otherwise, use the m_base_url value
399417
std::string base_url = host_url.length() > 0 ? host_url : m_base_url;

test/object-store/sync/app.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3282,6 +3282,48 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") {
32823282
}
32833283
}
32843284

3285+
3286+
TEST_CASE("app: trailing slash in base url", "[sync][app]") {
3287+
auto logger = util::Logger::get_default_logger();
3288+
3289+
const auto schema = get_default_schema();
3290+
3291+
SyncServer server({});
3292+
auto transport = std::make_shared<HookedTransport<UnitTestTransport>>();
3293+
auto socket_provider = std::make_shared<HookedSocketProvider>(logger, "");
3294+
OfflineAppSession::Config oas_config(transport);
3295+
oas_config.base_url = util::format("http://localhost:%1/", server.port());
3296+
oas_config.socket_provider = socket_provider;
3297+
OfflineAppSession oas(oas_config);
3298+
AutoVerifiedEmailCredentials creds;
3299+
auto app = oas.app();
3300+
const auto partition = random_string(100);
3301+
3302+
transport->request_hook = [&](const Request& req) -> std::optional<Response> {
3303+
if (req.url.find("/location") == std::string::npos) {
3304+
return std::nullopt;
3305+
}
3306+
3307+
REQUIRE(req.url == util::format("http://localhost:%1/api/client/v2.0/app/app_id/location", server.port()));
3308+
return Response{
3309+
200,
3310+
0,
3311+
{},
3312+
nlohmann::json(nlohmann::json::object({
3313+
{"hostname", util::format("http://localhost:%1", server.port())},
3314+
{"ws_hostname", util::format("ws://localhost:%1", server.port())},
3315+
{"sync_route", util::format("ws://localhost:%1/realm-sync", server.port())},
3316+
}))
3317+
.dump(),
3318+
};
3319+
};
3320+
3321+
SyncTestFile realm_config(oas, "test");
3322+
3323+
auto r = Realm::get_shared_realm(realm_config);
3324+
REQUIRE(!wait_for_download(*r));
3325+
}
3326+
32853327
TEST_CASE("app: redirect handling", "[sync][pbs][app]") {
32863328
auto logger = util::Logger::get_default_logger();
32873329

0 commit comments

Comments
 (0)