Add middlewares for API and various improvements (#685)

This commit is contained in:
Henry Wang
2018-09-12 05:00:47 +01:00
committed by DIYgod
parent 05ca3a6930
commit c835fa5c02
6 changed files with 247 additions and 48 deletions

View File

@@ -6,29 +6,23 @@ router.get('/routes/:name?', (ctx) => {
const allRoutes = Array.from(routes.stack);
allRoutes.shift();
const result = {};
let counter = 0;
allRoutes.forEach((i) => {
const path = i.path;
const top = path.split('/')[1];
if (ctx.params.name === undefined) {
if (ctx.params.name === undefined || top === ctx.params.name) {
if (result[top]) {
result[top].routes.push(path);
} else {
result[top] = { routes: [path] };
}
} else {
if (top === ctx.params.name) {
if (result[top]) {
result[top].routes.push(path);
} else {
result[top] = { routes: [path] };
}
}
counter++;
}
});
ctx.body = result;
ctx.body = { counter, result };
});
module.exports = router;

View File

@@ -93,6 +93,10 @@ RSSHub 同时支持 RSS 2.0、Atom 和 [JSON Feed](https://jsonfeed.org/) 输出
## API 接口
::: warning 注意
API 仍处于开发状态中,  并可能会有改动. 欢迎提供建议!
:::
RSSHub 提供下列 API 接口:
### 可用公共路由列表
@@ -109,12 +113,14 @@ RSSHub 提供下列 API 接口:
- name, 路由一级名称, 对应 [https://github.com/DIYgod/RSSHub/tree/master/routes](https://github.com/DIYgod/RSSHub/tree/master/routes) 中的文件夹名称. 可选, **缺省则返回所有可用路由**.
返回的 JSON 结果格式如下:
成功请求将会返回 HTTP 状态码 `200 OK` JSON 结果, 格式如下:
```js
{
"bilibili":{
"routes":[
"status": "success",
"data": {
"bilibili": {
"routes": [
"/bilibili/user/video/:uid",
"/bilibili/user/article/:uid",
"/bilibili/user/fav/:uid",
@@ -139,9 +145,13 @@ RSSHub 提供下列 API 接口:
"/bilibili/topic/:topic"
]
}
},
"message": "request returned 22 routes"
}
```
若无符合请求路由, 请求将会返回 HTTP 状态码 `204 No Content`.
## 社交媒体
### bilibili

View File

@@ -91,6 +91,10 @@ For exmaple:
## API
::: warning Warning
The API is under active development and is subject to change. All suggestions are welcome!
:::
RSSHub provides the following APIs:
### List of Public Routes
@@ -107,21 +111,27 @@ Parameters:
- name, route's top level name as in [https://github.com/DIYgod/RSSHub/tree/master/routes](https://github.com/DIYgod/RSSHub/tree/master/routes). Optional, **returns all public routes if not specified**.
The above example returns the following result in JSON:
A successful request returns a HTTP status code `200 OK` with the result in JSON:
```js
{
"github":{
"routes":[
"status": "success",
"data": {
"github": {
"routes": [
"/github/trending/:since/:language?",
"/github/issue/:user/:repo",
"/github/user/followers/:user",
"/github/stars/:user/:repo"
]
}
},
"message": "request returned 4 routes"
}
```
If no matching results were found, the server returns only a HTTP status code `204 No Content`.
## Application Updates
### RSSHub

View File

@@ -17,9 +17,14 @@ const auth = require('./middleware/auth');
const router = require('./router');
const protected_router = require('./protected_router');
const api_router = require('./api_router');
const mount = require('koa-mount');
// API related
const apiTemplate = require('./middleware/api-template');
const api_router = require('./api_router');
const apiResponseHandler = require('./middleware/api-response-handler');
process.on('uncaughtException', (e) => {
logger.error('uncaughtException: ' + e);
});
@@ -55,9 +60,11 @@ app.use(debug);
// 5 fix incorrect `utf-8` characters
app.use(utf8);
app.use(apiTemplate);
app.use(apiResponseHandler());
// 4 generate body
app.use(template);
// 3 filter content
app.use(parameter);

View File

@@ -0,0 +1,165 @@
/**
* HTTP Status codes
*/
const statusCodes = {
CONTINUE: 100,
OK: 200,
CREATED: 201,
ACCEPTED: 202,
NO_CONTENT: 204,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
REQUEST_TIMEOUT: 408,
UNPROCESSABLE_ENTITY: 422,
INTERNAL_SERVER_ERROR: 500,
NOT_IMPLEMENTED: 501,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIME_OUT: 504,
};
function responseHandler() {
return async (ctx, next) => {
ctx.res.statusCodes = statusCodes;
ctx.statusCodes = ctx.res.statusCodes;
ctx.res.success = ({ statusCode, data = null, message = null }) => {
const status = 'success';
if (!!statusCode && statusCode < 400) {
ctx.status = statusCode;
} else if (!(ctx.status < 400)) {
ctx.status = statusCodes.OK;
}
ctx.body = { status, data, message };
};
ctx.res.fail = ({ statusCode, code, data = null, message = null }) => {
const status = 'fail';
if (!!statusCode && (statusCode >= 400 && statusCode < 500)) {
ctx.status = statusCode;
} else if (!(ctx.status >= 400 && ctx.status < 500)) {
ctx.status = statusCodes.BAD_REQUEST;
}
ctx.body = { status, code, data, message };
};
ctx.res.error = ({ statusCode, code, data = null, message = null }) => {
const status = 'error';
if (!!statusCode && (statusCode >= 500 && statusCode < 600)) {
ctx.status = statusCode;
} else if (!(ctx.status >= 500 && ctx.status < 600)) {
ctx.status = statusCodes.INTERNAL_SERVER_ERROR;
}
ctx.body = { status, code, data, message };
};
ctx.res.ok = (params = {}) => {
ctx.res.success({
...params,
statusCode: statusCodes.OK,
});
};
ctx.res.created = (params = {}) => {
ctx.res.success({
...params,
statusCode: statusCodes.CREATED,
});
};
ctx.res.accepted = (params = {}) => {
ctx.res.success({
...params,
statusCode: statusCodes.ACCEPTED,
});
};
ctx.res.noContent = (params = {}) => {
ctx.res.success({
...params,
statusCode: statusCodes.NO_CONTENT,
});
};
ctx.res.badRequest = (params = {}) => {
ctx.res.fail({
...params,
statusCode: statusCodes.BAD_REQUEST,
});
};
ctx.res.forbidden = (params = {}) => {
ctx.res.fail({
...params,
statusCode: statusCodes.FORBIDDEN,
});
};
ctx.res.notFound = (params = {}) => {
ctx.res.fail({
...params,
statusCode: statusCodes.NOT_FOUND,
});
};
ctx.res.requestTimeout = (params = {}) => {
ctx.res.fail({
...params,
statusCode: statusCodes.REQUEST_TIMEOUT,
});
};
ctx.res.unprocessableEntity = (params = {}) => {
ctx.res.fail({
...params,
statusCode: statusCodes.UNPROCESSABLE_ENTITY,
});
};
ctx.res.internalServerError = (params = {}) => {
ctx.res.error({
...params,
statusCode: statusCodes.INTERNAL_SERVER_ERROR,
});
};
ctx.res.notImplemented = (params = {}) => {
ctx.res.error({
...params,
statusCode: statusCodes.NOT_IMPLEMENTED,
});
};
ctx.res.badGateway = (params = {}) => {
ctx.res.error({
...params,
statusCode: statusCodes.BAD_GATEWAY,
});
};
ctx.res.serviceUnavailable = (params = {}) => {
ctx.res.error({
...params,
statusCode: statusCodes.SERVICE_UNAVAILABLE,
});
};
ctx.res.gatewayTimeOut = (params = {}) => {
ctx.res.error({
...params,
statusCode: statusCodes.GATEWAY_TIME_OUT,
});
};
await next();
};
}
module.exports = responseHandler;

View File

@@ -0,0 +1,13 @@
module.exports = async (ctx, next) => {
await next();
if (ctx.request.path.startsWith('/api/')) {
if (ctx.body.counter > 0) {
return ctx.res.ok({
message: `request returned ${ctx.body.counter} ${ctx.body.counter > 1 ? 'routes' : 'route'}`,
data: ctx.body.result,
});
} else {
return ctx.res.noContent();
}
}
};