910e62b5创建于 1月15日历史提交
// 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