参考极客兔兔的7daysgolang
中的gee
,简易版的gin框架
极客兔兔的链接:https://geektutu.com/post/gee.html
可以简化workerjs开发,只需修改handleRequest
中的内容就可以,以下为示例代码
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `npm run deploy` to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/
class Engine {
constructor() {
this.name = 'gin';
this.router = new Router();
this._group = new RouterGroup(this);
this.groups = [];
this.groups.push(this._group);
}
addRoute(method, pattern, handler) {
this.router.addRoute(method, pattern, handler);
}
group(prefix) {
return this._group.group(prefix);
}
use(...middlewares) {
this._group.use(...middlewares);
}
get(pattern, handler) {
this.router.addRoute('GET', pattern, handler);
}
post(pattern, handler) {
this.router.addRoute('POST', pattern, handler);
}
delete(pattern, handler) {
this.router.addRoute('DELETE', pattern, handler);
}
put(pattern, handler) {
this.router.addRoute('PUT', pattern, handler);
}
patch(pattern, handler) {
this.router.addRoute('PATCH', pattern, handler);
}
options(pattern, handler) {
this.router.addRoute('OPTIONS', pattern, handler);
}
async serve(request) {
const ctx = new Context(request);
const middlewares = [];
for (let group of this.groups) {
if (ctx.path.startsWith(group.prefix)) {
middlewares.push(...group.middlewares);
}
}
ctx.handlers = middlewares;
return this.router.handle(ctx);
}
}
class Context {
constructor(request) {
this.request = request;
this.headers = {};
const url = new URL(request.url);
this._url = url;
const params = new URLSearchParams(url.search);
this.path = url.pathname;
this._query = Object.fromEntries(params.entries());
this.status = 200;
this.method = request.method;
this.params = {};
this.handlers = [];
this.index = -1;
}
next() {
this.index++;
let len = this.handlers.length;
for (; this.index < len; this.index++) {
return this.handlers[this.index](this);
}
}
query(key) {
return this._query[key];
}
param(key) {
return this.params[key];
}
setHeader(key, value) {
this.headers[key] = value;
}
setStatus(code) {
this.status = code;
}
string(code, data) {
this.setHeader('Content-Type', 'text/plain');
this.setStatus(code);
return new Response(data, {
headers: this.headers,
status: this.status,
});
}
json(code, data) {
this.setHeader('Content-Type', 'application/json');
this.setStatus(code);
return new Response(JSON.stringify(data), {
headers: this.headers,
status: this.status,
});
}
data(code, data) {
this.setStatus(code);
return new Response(data, {
headers: this.headers,
status: this.status,
});
}
html(code, data) {
this.setHeader('Content-Type', 'text/html');
this.setStatus(code);
return new Response(data, {
headers: this.headers,
status: this.status,
});
}
}
class Router {
constructor() {
this.roots = new Map(); // [(get, post, put, delete, patch, options), treeNode]
this.handlers = new Map();
}
addRoute(method, pattern, handler) {
const parts = parsePattern(pattern);
const key = method + '-' + pattern;
if (!this.roots.has(method)) {
this.roots.set(method, new treeNode('', '', false));
}
this.roots.get(method).insert(pattern, parts, 0);
this.handlers.set(key, handler);
}
getRoute(method, path) {
const searchParts = parsePattern(path);
const params = {};
const root = this.roots.get(method);
if (!root) {
return {
node: null,
params,
};
}
const node = root.search(searchParts, 0);
if (node) {
const parts = parsePattern(node.pattern);
for (let i = 0; i < parts.length; i++) {
let part = parts[i];
if (part[0] === ':') {
params[part.slice(1)] = searchParts[i];
continue;
}
if (part[0] === '*' && parts[i].length > 1) {
params[part.slice(1)] = searchParts.slice(i).join('/');
break;
}
}
return {
node,
params,
};
}
return {
node: null,
params,
};
}
async handle(ctx) {
const { node, params } = this.getRoute(ctx.method, ctx.path);
if (node) {
ctx.params = params;
const key = ctx.method + '-' + node.pattern;
const handler = this.handlers.get(key);
ctx.handlers.push(handler);
// if (handler) {
// return handler(ctx);
// }
} else {
ctx.handlers.push(async (ctx) => {
return ctx.string(404, '404 Not Found');
});
}
// return ctx.string(404, '404 Not Found');
return ctx.next();
}
}
class RouterGroup {
constructor(engine) {
this.prefix = '';
this.middlewares = [];
this.parent = null;
this.engine = engine;
}
group(prefix) {
const newGroup = new RouterGroup(this.engine);
newGroup.prefix = this.prefix + prefix;
// newGroup.middlewares = middlewares;
newGroup.parent = this;
this.engine.groups.push(newGroup);
return newGroup;
}
use(...middlewares) {
this.middlewares.push(...middlewares);
}
addRoute(method, pattern, handler) {
this.engine.addRoute(method, this.prefix + pattern, handler);
}
get(pattern, handler) {
this.engine.get(this.prefix + pattern, handler);
}
post(pattern, handler) {
this.engine.post(this.prefix + pattern, handler);
}
delete(pattern, handler) {
this.engine.delete(this.prefix + pattern, handler);
}
put(pattern, handler) {
this.engine.put(this.prefix + pattern, handler);
}
patch(pattern, handler) {
this.engine.patch(this.prefix + pattern, handler);
}
options(pattern, handler) {
this.engine.options(this.prefix + pattern, handler);
}
}
function parsePattern(pattern) {
const parts = pattern.split('/');
const nodes = [];
for (let part of parts) {
if (part !== '') {
nodes.push(part);
if (part[0] === '*') {
break;
}
}
}
return nodes;
}
class treeNode {
constructor(pattern, part, isWild) {
this.pattern = pattern ?? ''; // 待匹配路由,例如 /p/:lang
this.part = part; // 路由中的一部分,例如 :lang
this.isWild = isWild; // 是否精确匹配,part 含有 : 或 * 时为true
this.children = [];
}
matchChild(path) {
for (let child of this.children) {
if (child.part === path || child.isWild) {
return child;
}
}
}
matchChildren(path) {
let nodes = [];
for (let child of this.children) {
if (child.part === path || child.isWild) {
nodes.push(child);
}
}
nodes.sort((a, b) => {
return a.part.length - b.part.length;
});
return nodes;
}
insert(pattern, parts, height) {
if (parts.length === height) {
this.pattern = pattern;
return;
}
const part = parts[height];
let child = this.matchChild(part);
if (!child) {
child = new treeNode('', part, part[0] === ':' || part[0] === '*');
this.children.push(child);
}
child.insert(pattern, parts, height + 1);
}
search(parts, height) {
if (parts.length === height || this.part.startsWith('*')) {
if (this.pattern === '') {
return;
}
return this;
}
const part = parts[height];
const children = this.matchChildren(part);
for (let child of children) {
let node = child.search(parts, height + 1);
if (node) {
return node;
}
}
return null;
}
}
const logMiddleware = (ctx) => {
const start = new Date();
const response = ctx.next();
const duration = Date.now() - start;
// console.log('log middleware', ctx.path, duration, 'ms');
return response; // 返回响应
};
const authMiddleware = (ctx) => {
const token = ctx.query('token');
if (!token) {
return ctx.string(401, '401 Unauthorized');
}
return ctx.next();
};
const testIPMiddleware = async (ctx) => {
const response = await fetch('http://ip-api.com/json');
const data = await response.json();
ctx.setHeader('X-IP', data.query);
const resp = ctx.next();
return resp;
};
const corsMiddleware = async (ctx) => {
const origin = ctx.request.headers.get('Origin');
// 允许cookie
ctx.setHeader('Access-Control-Allow-Origin', origin);
ctx.setHeader('Access-Control-Allow-Credentials', 'true');
if (ctx.method === 'OPTIONS') {
ctx.setHeader('Access-Control-Allow-Methods', ctx.request.headers.get('Access-Control-Request-Method'));
ctx.setHeader('Access-Control-Allow-Headers', ctx.request.headers.get('Access-Control-Request-Headers'));
ctx.setHeader('Access-Control-Max-Age', '7200');
ctx.setStatus(204);
return ctx.data(204, null);
}
return ctx.next();
};
const newEngine = () => {
return new Engine();
};
const handleRequest = () => {
const router = newEngine();
router.use(logMiddleware);
router.use(corsMiddleware);
router.get('/', async (ctx) => {
return ctx.string(200, 'Hello GIN');
});
router.get('/self/ip', async (ctx) => {
const response = await fetch('http://ip-api.com/json');
return response;
});
router.get('/self/ip2', async (ctx) => {
const response = await fetch('http://ip-api.com/json');
const data = await response.json();
return ctx.json(200, data);
});
router.get('/about', async (ctx) => {
const name = ctx.query('name');
return ctx.json(200, {
name,
});
});
router.post('/v1/chat/completions', async (ctx) => {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer sk-1234567890xxx',
},
body: ctx.request.body,
});
const newHeaders = new Headers(response.headers);
for (const [key, value] of newHeaders.entries()) {
ctx.setHeader(key, value);
}
return ctx.data(200, response.body);
});
router.get('/about/:name', async (ctx) => {
const name = ctx.param('name');
return ctx.json(200, {
name,
});
});
const api = router.group('/api');
api.use(authMiddleware);
api.use(testIPMiddleware);
api.get('/ping', async (ctx) => {
return ctx.string(200, 'pong');
});
api.get('/upload/*filepath', async (ctx) => {
const filepath = ctx.param('filepath');
return ctx.json(200, {
filepath,
});
});
const userApi = api.group('/user');
userApi.get('/:id', async (ctx) => {
const id = ctx.param('id');
return ctx.json(200, {
id,
name: 'sky',
age: 18,
});
});
return async (request) => router.serve(request);
};
const listener = handleRequest();
addEventListener('fetch', (event) => {
// event.respondWith(router.serve(event.request));
event.respondWith(listener(event.request));
});