Skip to content

HTTP Routing Filter Missing POST Body and Query String Issues #203

Description

@bettercallsaulj

What's the Issue

The HttpRoutingFilter had two related problems:

Issue 1: POST/PUT/PATCH Handlers Cannot Access Request Body

Route handlers were executed immediately in onHeaders() before the request body was received. This meant POST/PUT/PATCH handlers always received an empty body field, making it impossible to process request payloads.

// Handler registered for POST endpoint
filter->registerHandler("POST", "/api/data", [](const RequestContext& req) {
    // req.body is ALWAYS empty - body hasn't arrived yet!
    auto data = parseJson(req.body);  // Fails
    return Response{200, "OK"};
});

Issue 2: Query Strings Break Route Matching

Routes were matched against the full URL path including query strings. A request to /search?q=test would not match a handler registered for /search.

filter->registerHandler("GET", "/search", handler);

// Request: GET /search?q=test&page=1
// Expected: Routes to /search handler
// Actual: No handler found (404) because "/search?q=test&page=1" != "/search"

How to Reproduce

Reproducing Issue 1 (Missing POST Body):

  1. Register a POST handler:

    filter->registerHandler("POST", "/api/users", [](const RequestContext& req) {
        std::cout << "Body: " << req.body << std::endl;  // Empty!
        return Response{201, "Created"};
    });
  2. Send a POST request with JSON body:

    curl -X POST http://localhost:8080/api/users \
         -H "Content-Type: application/json" \
         -d '{"name": "John", "email": "john@example.com"}'
  3. Expected: Handler receives the JSON body

  4. Actual: Handler receives empty string for req.body

Reproducing Issue 2 (Query String Routing):

  1. Register a GET handler:

    filter->registerHandler("GET", "/search", [](const RequestContext& req) {
        return Response{200, "Search results"};
    });
  2. Send a request with query parameters:

    curl "http://localhost:8080/search?q=hello&limit=10"
  3. Expected: Routes to /search handler, handler sees full URL in req.path

  4. Actual: Returns 404 because no handler matches /search?q=hello&limit=10

How to Fix

Fix 1: Defer POST/PUT/PATCH Handler Execution

Add state to accumulate the body and defer handler execution until onMessageComplete():

In http_routing_filter.h:

class HttpRoutingFilter {
 private:
  // State for POST requests that need body
  bool pending_post_request_ = false;
  RequestContext pending_context_;
  HandlerFunc pending_handler_;
  std::string accumulated_body_;
};

In onHeaders() - defer POST/PUT/PATCH:

if (method == "POST" || method == "PUT" || method == "PATCH") {
  pending_post_request_ = true;
  pending_context_ = ctx;
  pending_handler_ = handler_it->second;
  accumulated_body_.clear();
  return;  // Wait for body
}

In onBody() - accumulate body:

if (pending_post_request_) {
  accumulated_body_ += data;
  return;  // Don't forward - we'll handle in onMessageComplete
}

In onMessageComplete() - execute handler with full body:

if (pending_post_request_) {
  pending_context_.body = accumulated_body_;
  Response resp = pending_handler_(pending_context_);
  if (resp.status_code != 0) {
    sendResponse(resp);
  }
  pending_post_request_ = false;
  accumulated_body_.clear();
  return;
}

Fix 2: Strip Query String for Routing, Preserve for Handler

In extractPath() - strip query string:

std::string HttpRoutingFilter::extractPath(const Headers& headers) {
  std::string full_path = /* get from headers */;

  // Strip query string for routing purposes
  size_t query_pos = full_path.find('?');
  if (query_pos != std::string::npos) {
    return full_path.substr(0, query_pos);
  }
  return full_path;
}

In onHeaders() - provide full URL to handler:

RequestContext ctx;
ctx.method = method;
ctx.path = full_url;  // Full URL with query string for handler access
ctx.headers = headers;

Files changed:

  • include/mcp/filter/http_routing_filter.h - Add state variables
  • src/filter/http_routing_filter.cc - Implement body accumulation and query string handling

Tests added:

  • tests/filter/test_http_routing_filter_simple.cc
    • PostBodyHandling - Verifies POST handlers wait for body
    • PostBodyAccumulation - Verifies chunked body accumulation
    • QueryStringRouting - Verifies /search?q=test routes to /search
    • PutBodyHandling - Verifies PUT handlers receive body
    • GetRequestImmediateExecution - Verifies GET still executes immediately

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions