Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ysysimon opened this issue Sep 30, 2024 · 8 comments
Assignees

Comments

@ysysimon
Copy link

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
Copy link
Member

an-tao commented Sep 30, 2024

Would you like to paste your code here?

@ysysimon
Copy link
Author

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
Copy link
Author

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
Copy link
Member

an-tao commented Oct 1, 2024

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
Copy link
Author

ysysimon commented Oct 1, 2024

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
Copy link
Member

hwc0919 commented Oct 1, 2024

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
Copy link
Author

ysysimon commented Oct 1, 2024

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
});
  1. 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

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
Copy link
Author

ysysimon commented Oct 1, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants