blob: c2a5fe2cae5404a204a524ad53074f16de94cc74 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/test/embedded_test_server/expectation_handler.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
namespace net::test_server {
// UrlResponseConfig implementation
struct ExpectationHandler::UrlResponseConfig {
UrlResponseConfig() = default;
~UrlResponseConfig() = default;
// Whether the URL Path is a prefix match
bool is_prefix = false;
HttpStatusCode status_code;
// Content type of the response (e.g., "text/html")
std::string_view content_type;
std::string_view content;
base::OnceCallback<void(HttpRequest)> value_setting_callback;
};
// ExpectationHandler implementation
ExpectationHandler::ExpectationHandler(EmbeddedTestServer* embedded_test_server)
: embedded_test_server_(*embedded_test_server) {
embedded_test_server_->RegisterRequestHandler(base::BindRepeating(
&ExpectationHandler::HandleRequest, base::Unretained(this)));
}
ExpectationHandler::~ExpectationHandler() = default;
ExpectationHandler::ResponseBuilder ExpectationHandler::OnRequest(
std::string_view path,
bool is_prefix) {
// Returns a ResponseBuilder that configures how to respond to requests for
// this URL
return ResponseBuilder(*this, path, is_prefix);
}
std::unique_ptr<HttpResponse> ExpectationHandler::HandleRequest(
const HttpRequest& request) {
base::AutoLock auto_lock(lock_);
// First, try to find an exact match using lower_bound
const auto lower_bound = url_responses_.lower_bound(request.relative_url);
if (lower_bound != url_responses_.end() &&
lower_bound->first == request.relative_url) {
return ApplyConfig(request, lower_bound->second.get());
}
// Iterate backwards from lower_bound to find the longest prefix match
auto it = lower_bound;
while (it != url_responses_.begin()) {
--it;
const auto& config = it->second;
if (!config->is_prefix) {
continue;
}
if (base::StartsWith(request.relative_url, it->first)) {
return ApplyConfig(request, config.get());
}
}
return nullptr;
}
// Helper method to apply a configuration and create a response
std::unique_ptr<HttpResponse> ExpectationHandler::ApplyConfig(
const HttpRequest& request,
raw_ptr<UrlResponseConfig> config) {
if (config->value_setting_callback) {
std::move(config->value_setting_callback).Run(request);
}
if (!config->content_type.empty()) {
auto response = std::make_unique<BasicHttpResponse>();
response->set_code(config->status_code);
response->set_content_type(config->content_type);
response->set_content(config->content);
return response;
}
return nullptr;
}
// ResponseBuilder implementation
ExpectationHandler::ResponseBuilder::ResponseBuilder(
ExpectationHandler& handler,
std::string_view path,
bool is_prefix)
: handler_(handler), path_(path) {
base::AutoLock auto_lock(handler_->lock_);
auto new_config = std::make_unique<ExpectationHandler::UrlResponseConfig>();
new_config->is_prefix = is_prefix;
config_ = new_config.get();
handler_->url_responses_[path_] = std::move(new_config);
}
ExpectationHandler::ResponseBuilder::~ResponseBuilder() = default;
ExpectationHandler::ResponseBuilder&
ExpectationHandler::ResponseBuilder::RespondWith(
const HttpStatusCode status_code,
std::string_view content_type,
std::string_view content) {
base::AutoLock auto_lock(handler_->lock_);
if (config_) {
config_->status_code = status_code;
config_->content_type = content_type;
config_->content = content;
}
return *this;
}
ExpectationHandler::ResponseBuilder&
ExpectationHandler::ResponseBuilder::RespondWith(std::string_view content_type,
std::string_view content) {
return RespondWith(HTTP_OK, content_type, content);
}
ExpectationHandler::ResponseBuilder&
ExpectationHandler::ResponseBuilder::SetValue(
base::test::TestFuture<HttpRequest>& future) {
base::AutoLock auto_lock(handler_->lock_);
config_->value_setting_callback = future.GetSequenceBoundCallback();
return *this;
}
} // namespace net::test_server