drogonframework / drogon

Drogon: A C++14/17/20 based HTTP web application framework running on Linux/macOS/Unix/Windows
MIT License
11.48k stars 1.1k forks source link

When using a coroutine controller, the response header modified by the middleware has no effect #2173

Open ysysimon opened 1 week ago

ysysimon commented 1 week ago

I'm trying to use a CROS middleware with my coroutine controller, but no matter what I try, the options request goes through fine, but subsequent requests still lack the Access-Control-Allow request header. I've tried using both HttpCoroMiddleware and HttpMiddleware

an-tao commented 1 week ago

Would you like to paste your code here?

ysysimon commented 1 week ago
Task<HttpResponsePtr> CORSMiddleware::invoke(const HttpRequestPtr &req, MiddlewareNextAwaiter &&next)
{
    const std::string& origin = req->getHeader("Origin");
    // 检查是否允许所有来源 (通配符 *)
    bool allowAllOrigins = (allowedOrigins_.find("*") != allowedOrigins_.end());
    spdlog::info("被调用!!!");

    // 处理 OPTIONS 预检请求
    if (req->method() == Options)
    {
        spdlog::info("CORSMiddleware invoked by {} 跨域中间件被调用", origin);
        // 检查 Origin 是否被允许
        if (!allowAllOrigins && allowedOrigins_.find(origin) == allowedOrigins_.end())
        {
            auto resp = HttpResponse::newHttpResponse();
            resp->setStatusCode(k403Forbidden);
            resp->setBody("403 Forbidden - Origin not allowed 非法跨域请求");
            mcb(resp);  // 返回响应,终止进一步处理
            spdlog::warn("Origin {} not allowed, 403 Forbidden 非法跨域请求", origin);
            return;
        }
        // 返回预检请求响应
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k200OK);
        if (allowAllOrigins)
        {
            resp->addHeader("Access-Control-Allow-Origin", "*");
        }
        else
        {
            resp->addHeader("Access-Control-Allow-Origin", origin);
        }
        resp->addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
        resp->addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        resp->addHeader("Access-Control-Allow-Credentials", "true");
        mcb(resp);  // 返回响应,终止进一步处理
        return;
    }

or Coroutine version

Task<HttpResponsePtr> CORSMiddleware::invoke(const HttpRequestPtr &req, MiddlewareNextAwaiter &&next)
{
    const std::string& origin = req->getHeader("Origin");
    // 检查是否允许所有来源 (通配符 *)
    bool allowAllOrigins = (allowedOrigins_.find("*") != allowedOrigins_.end());
    spdlog::info("被调用!!!");

    // 处理 OPTIONS 预检请求
    if (req->method() == Options)
    {
        spdlog::info("CORSMiddleware invoked by {} 跨域中间件被调用", origin);

        // 检查 Origin 是否被允许
        if (!allowAllOrigins && allowedOrigins_.find(origin) == allowedOrigins_.end())
        {
            auto resp = HttpResponse::newHttpResponse();
            resp->setStatusCode(k403Forbidden);
            resp->setBody("403 Forbidden - Origin not allowed 非法跨域请求");
            spdlog::warn("Origin {} not allowed, 403 Forbidden 非法跨域请求", origin);
            co_return resp;  // 返回响应,终止进一步处理
        }

        // 返回预检请求响应
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k200OK);
        if (allowAllOrigins)
        {
            resp->addHeader("Access-Control-Allow-Origin", "*");
        }
        else
        {
            resp->addHeader("Access-Control-Allow-Origin", origin);
        }
        resp->addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
        resp->addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        resp->addHeader("Access-Control-Allow-Credentials", "true");

        co_return resp;  // 返回响应,终止进一步处理
    }

    // 继续处理普通请求,等待下一个中间件或控制器
    auto resp = co_await next;  // 继续请求处理

    spdlog::info("普通请求");

    // 在响应中添加 CORS 头
    if (allowAllOrigins)
    {
        resp->addHeader("Access-Control-Allow-Origin", "*");
    }
    else
    {
        resp->addHeader("Access-Control-Allow-Origin", origin);
    }
    resp->addHeader("Access-Control-Allow-Credentials", "true");

    co_return resp;  // 返回最终的响应
}

header file is like

class CORSMiddleware : public drogon::HttpCoroMiddleware<CORSMiddleware>
{
public:
    static constexpr bool isAutoCreation = false;  // 明确设置为 false
    CORSMiddleware(const std::unordered_set<std::string>& allowedOrigins);

    Task<HttpResponsePtr> invoke(const HttpRequestPtr &req,
                                MiddlewareNextAwaiter &&next) override;

private:
    std::unordered_set<std::string> allowedOrigins_;
};

and I try to register it this way

auto corsMiddleware = std::make_shared<YLineServer::CORSMiddleware>(config.allowed_origins);
        drogon::app().registerMiddleware(corsMiddleware);
        // 将协程中间件注册为 Pre-Routing Advice
        // 注册 Pre-Routing Advice
        drogon::app().registerPreRoutingAdvice([corsMiddleware](const HttpRequestPtr &req, AdviceCallback &&callback, AdviceChainCallback &&chainCallback) {
            // 调用中间件的 invoke 方法
            corsMiddleware->invoke(req,
                // nextCb:继续执行下一个中间件或控制器的回调
            [chainCallback = std::move(chainCallback)](const std::function<void(const HttpResponsePtr &)>& nextResponseCb) {
                        // 执行下一个 Pre-Routing Advice 或进入路由处理
                        chainCallback();
                    },
                // mcb:终止请求并返回响应的回调
                [callback = std::move(callback)](const HttpResponsePtr &resp) {
                    // 返回响应
                    callback(resp);
                }
            );
ysysimon commented 1 week ago

When I use HttpCoroMiddleware, the middleware cannot be used. I don't know the correct way to manually register it (via registerPreRoutingAdvice). Even if register it through the ADD_PATH_TO macro, it can't work correctly. When I use HttpMiddleware, the Option request can be completed normally, but subsequent POST and other requests still cannot add the correct "Access-Control-Allow" request header information. 当我使用 HttpCoroMiddleware ,中间件无法使用,我不知道手动注册它的正确方法 (通过 registerPreRoutingAdvice),即使通过 ADD_PATH_TO 宏通过了编译,也无法正确工作,而当我使用 HttpMiddleware,Option 请求可以正常完成,但是后续的 POST 等请求仍然无法 添加正确的 “Access-Control-Allow” 请求头信息

an-tao commented 1 week ago

Middleware in drogon is not global, after registering your middleware to framework, did you add the name of middleware to your routing path in controllers?

ysysimon commented 1 week ago

Middleware in drogon is not global, after registering your middleware to framework, did you add the name of middleware to your routing path in controllers?

yes, I tried both ‘registerPreRoutingAdvice’ and ADD_PATH_TO

hwc0919 commented 1 week ago

option requests do not enter middlewares or handlers. They are handled directly by framework during routing. not all true, I'll look into it later.

ysysimon commented 1 week ago

According to my test, option request is not automatically handled by the framework, I have to add middleware to handle it. The situation as follows:

  1. My controller is a coroutine controller
  2. I inherit HttpMiddleware (non-coroutine middleware) and override the ivoke method, and then use
    drogon::app().registerPreRoutingAdvice

    to add the route of it as a global middleware

  3. After doing this, the browser's Option request is correctly processed, but subsequent post requests do not carry the correct CORS header, even though I have done this in the middleware for normal requests
    nextCb([origin, mcb = std::move(mcb), allowAllOrigins](const HttpResponsePtr& resp) mutable {
    spdlog::info("normal request");
    // Add CORS header to the response
    if (allowAllOrigins)
    {
    resp->addHeader("Access-Control-Allow-Origin", "*");
    }
    else
    {
    resp->addHeader("Access-Control-Allow-Origin", origin);
    }
    resp->addHeader("Access-Control-Allow-Credentials", "true");
    mcb(resp); // Return the final response
    });
  4. Then I suspected that the callback(resp) executed in the coroutine controller directly returned the request to the browser client, causing nextCb to not receive the response returned by the controller, so I suspected that the coroutine version of the middleware must be used with the coroutine version of the controller, so
    
    Task<HttpResponsePtr> CORSMiddleware::invoke(const HttpRequestPtr &req, MiddlewareNextAwaiter &&next)
    {
    const std::string& origin = req->getHeader("Origin");
    // Check if all origins are allowed (wildcard *)
    bool allowAllOrigins = (allowedOrigins_.find("*") != allowedOrigins_.end());
    spdlog::info("Called!!!");

// Handle OPTIONS pre-check request if (req->method() == Options) { spdlog::info("CORSMiddleware invoked by {} Cross-origin middleware is called", origin);

// Check if Origin is allowed if (!allowAllOrigins && allowedOrigins.find(origin) == allowedOrigins.end()) { auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k403Forbidden); resp->setBody("403 Forbidden - Origin not allowed Illegal cross-domain request"); spdlog::warn("Origin {} not allowed, 403 Forbidden Illegal cross-domain request", origin); co_return resp; // Return response and terminate further processing }

// Return pre-check request response auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k200OK); if (allowAllOrigins) { resp->addHeader("Access-Control-Allow-Origin", "*"); } else { resp->addHeader("Access-Control-Allow-Origin", origin); } resp->addHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS"); resp->addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); resp->addHeader("Access-Control-Allow-Credentials", "true");

co_return resp; // Return response and terminate further processing }

// Continue processing normal requests and wait for the next middleware or controller auto resp = co_await next; // Continue request processing

spdlog::info("Normal request");

// Add CORS header to the response if (allowAllOrigins) { resp->addHeader("Access-Control-Allow-Origin", "*"); } else { resp->addHeader("Access-Control-Allow-Origin", origin); } resp->addHeader("Access-Control-Allow-Credentials", "true");

co_return resp; // Return the final response }

But this time I don't know how to use
```cpp
drogon::app().registerPreRoutingAdvice

to add the route of this coroutine middleware, so I manually added it in the ADD_PATH_TO macro of the controller, but this time even the Option request was not handled correctly

ysysimon commented 1 week ago

Ok, I seem to have misunderstood it, I thought the generated response would also go through the middleware to be add CORS header

I am confused, only request will go through the middleware and not respond? It seems that the fact is that only the option request goes through the full onion ring, while the subsequent post request, etc., only the request enters, but the respond does not. It is directly returned to the client, resulting in no corresponding response header information being added.