基于 Content-Type 标头的 Expressjs 路由器

Posted

技术标签:

【中文标题】基于 Content-Type 标头的 Expressjs 路由器【英文标题】:Expressjs router based on Content-Type header 【发布时间】:2016-06-24 03:16:30 【问题描述】:

对于路由,我希望我的中间件将 /html 文件夹中定义的路由请求传递给服务器 HTML(ejs),如果标头 Content-Type 是 application/json,请使用 /api 中定义的路由文件夹。

但我不想在每条路线中都定义它。 所以我不是在寻找定义一些我可以在每条路由中检查的 req.api 属性的中间件

app.get('/', function(req, res) 
    if(req.api_call) 
        // serve api
     else 
        // serve html
    
);

但我想要这样的东西:

// HTML folder
app.get('/', function(req, res) 
    res.send('hi');
);

// API folder
app.get('/', function(req, res) 
    res.json(message: 'hi');
);

这可能吗?如果可以,我该怎么做?

我希望它像这样工作:

app.use(checkApiCall, apiRouter);
app.use(checkHTMLCall, htmlRouter);

【问题讨论】:

查看这里关于检查请求内容类型 - ***.com/questions/18902293/… 我知道如何检查请求内容类型。我面临的问题是想要使用基于 Content-Type 的不同快速路由器,而不仅仅是像 app.use('/api', apiRouter); 这样的 url 由于路由器本质上是按路径工作的,我想知道您是否可以使用一些中间件来检查内容类型并将路径修改为包含表示内容类型的内容的伪路径,然后设置您的路由器为修改后的路径提供服务。例如,对 JSON 内容类型的 '/' 的请求可以修改为 '/api/' 的伪路径,因此您可以使用 app.get('/api/', ...) 来提供它。我不完全确定在您修改请求中的路径后路由器仍然可以工作,但您可以尝试一下。 如果不在路由器函数中添加那些if 语句可能是不可能的,因为app.use() 似乎没有实现任何额外的限制可能性,但路径。 @jfriend00 路径不是只读的吗? 【参考方案1】:

您可以作为 Express 链中的第一个中间件插入一个中间件处理程序,该处理程序检查请求类型,然后通过向其添加前缀路径将 req.url 修改为伪 URL。然后,此修改将强制该请求仅发送到特定路由器(设置为处理该特定 URL 前缀的路由器)。我已使用以下代码在 Express 中验证了此功能:

var express = require('express');
var app = express();
app.listen(80);

var routerAPI = express.Router();
var routerHTML = express.Router();

app.use(function(req, res, next) 
    // check for some condition related to incoming request type and
    // decide how to modify the URL into a pseudo-URL that your routers
    // will handle
    if (checkAPICall(req)) 
        req.url = "/api" + req.url;
     else if (checkHTMLCall(req)) 
        req.url = "/html" + req.url;
    
    next();
);

app.use("/api", routerAPI);
app.use("/html", routerHTML);

// this router gets hit if checkAPICall() added `/api` to the front
// of the path
routerAPI.get("/", function(req, res) 
    res.json(status: "ok");
);

// this router gets hit if checkHTMLCall() added `/api` to the front
// of the path
routerHTML.get("/", function(req, res) 
    res.end("status ok");
);

注意:我没有填写 checkAPICall()checkHTMLCall() 的代码,因为您没有完全具体说明您希望它们如何工作。我在自己的测试服务器中模拟了它们,以查看这个概念是否有效。我假设您可以为这些函数提供适当的代码或替换您自己的 if 语句。

之前的答案

我刚刚验证了您可以在 Express 中间件中更改 req.url,因此如果您有一些修改了 req.url 的中间件,则会影响该请求的路由。

// middleware that modifies req.url into a pseudo-URL based on 
// the incoming request type so express routing for the pseudo-URLs
// can be used to distinguish requests made to the same path 
// but with a different request type
app.use(function(req, res, next) 
    // check for some condition related to incoming request type and
    // decide how to modify the URL into a pseudo-URL that your routers
    // will handle
    if (checkAPICall(req)) 
        req.url = "/api" + req.url;
     else if (checkHTMLCall(req)) 
        req.url = "/html" + req.url;
    
    next();
);

// this will get requests sent to "/" with our request type that checkAPICall() looks for
app.get("/api/", function(req, res) 
    res.json(status: "ok");
);

// this will get requests sent to "/" with our request type that checkHTMLCall() looks for
app.get("/html/", function(req, res) 
    res.json(status: "ok");
);

旧答案

我能够像这样成功地将请求回调放在 express 前面,并看到它成功地修改了传入的 URL,然后像这样影响 express 路由:

var express = require('express');
var app = express();
var http = require('http');

var server = http.createServer(function(req, res) 
    // test modifying the URL before Express sees it
    // this could be extended to examine the request type and modify the URL accordingly
    req.url = "/api" + req.url;
    return app.apply(this, arguments);
);

server.listen(80);

app.get("/api/", function(req, res) 
    res.json(status: "ok");
);

app.get("/html/", function(req, res) 
    res.end("status ok");
);

这个例子(我测试过)只是硬连线在 URL 的前面添加“/api”,但您可以自己检查传入的请求类型,然后根据需要进行 URL 修改。我还没有探索这是否可以完全在 Express 中完成。

在这个例子中,当我请求“/”时,我得到了 JSON。

【讨论】:

为答案添加了几个版本。最新显示,中间件可以将 URL 修改为伪路径,然后由 Express 路由器匹配。因此,您可以在自己的路由器中拥有每组请求和一个简单的中间件函数,只需将 req.url 修改为伪 URL,即可将请求定向到适当的路由器。 恕我直言,我认为这是一个“肮脏”的黑客,但它确实有效,所以 w/e xD 谢谢!我真的做到了!太棒了! @CreasolDev - 这有点像 hack,但它是一个非常干净的 hack,因为它唯一依赖的是您可以在早期的中间件中更改 req.url,该中间件用于其他原因也。除此之外,它是直接路由,所以它实际上是一个非常干净的 hack。【参考方案2】:

为了把我的帽子放在戒指上,我想要易于阅读的路线,而不是到处都有.json 后缀。

router.get("/foo", HTML_ACCEPTED, (req, res) => res.send("<html><h1>baz</h1><p>qux</p></html>"))
router.get("/foo", JSON_ACCEPTED, (req, res) => res.json(foo: "bar"))

这些中间件的工作原理如下。

function HTML_ACCEPTED (req, res, next)  return req.accepts("html") ? next() : next("route") 
function JSON_ACCEPTED (req, res, next)  return req.accepts("json") ? next() : next("route") 

我个人认为这是非常可读的(因此是可维护的)。

$ curl localhost:5000/foo --header "Accept: text/html"
<html><h1>baz</h1><p>qux</p></html>

$ curl localhost:5000/foo --header "Accept: application/json"
"foo":"bar"

注意事项:

我建议将HTML 路由放在JSON 路由之前,因为某些浏览器会接受HTML JSON,因此它们会获取首先列出的任何路由。我希望 API 用户能够理解和设置 Accept 标头,但我不希望浏览器用户如此,因此浏览器会获得偏好。 ExpressJS Guide 的最后一段谈到了next('route')。简而言之,next() 跳到下一个中​​间件在同一路径,而next('route') 跳出这条路径并尝试下一个。 这是req.accepts 上的参考资料。

【讨论】:

HTML_ACCEPTED 和 JSON_ACCEPTED` 函数在 router.get 定义中看起来很干净,感觉很干净。不过到了这个时候,我决定采用“单点出口”类型的路由,所有不同的路由只处理所需的数据并将其全部传递给最后一个ResponseRouter,它基于@提供数据987654335@。它为 HTML 呈现 EJS 页面或为Content-Type: Application/JSON 提供 JSON 响应但感谢您的响应,看起来很棒!

以上是关于基于 Content-Type 标头的 Expressjs 路由器的主要内容,如果未能解决你的问题,请参考以下文章

不要在 cURL 中发送任何 Content-type 标头

带有自定义标头的 C# HttpClient POST 请求发送不正确的 Content-Type 标头字段

使用 MultipartFormDataContent 生成的错误 Content-Type 标头

从 POSTED 标头获取图像文件名('Content-type: image.jpeg')

由 Quarkus 提供的带有错误 Content-Type 标头的 Wasm 文件

在 golang HTTP FileServer 的 Content-Type 标头上设置 'charset' 属性