mirror of https://github.com/186526/handlers.js
Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
186526 | 7051a95fb9 | |
186526 | 89c5ed561a | |
186526 | 79b9fba1cd | |
186526 | c216c0a62f | |
186526 | fccf33d953 | |
186526 | f199ef7f33 | |
186526 | b1558bd9fa |
|
@ -8,7 +8,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
node-version: [18.x, 20.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 4
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"compile-hero.disable-compile-files-on-did-save-code": true,
|
||||
"deno.enable": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnSave": true
|
||||
}
|
|
@ -3,13 +3,13 @@
|
|||
> Handlers.js is a unified and lightweight web application framework for multiple platforms.
|
||||
|
||||
```ts
|
||||
import handlerJS from "handlers.js";
|
||||
import handlerJS from 'handlers.js';
|
||||
|
||||
const App = new handlerJS();
|
||||
|
||||
App.binding(
|
||||
"/",
|
||||
App.create("ANY", async () => "Hello World!")
|
||||
'/',
|
||||
App.create('ANY', async () => 'Hello World!'),
|
||||
);
|
||||
|
||||
App.useMappingAdapter();
|
||||
|
|
|
@ -1,87 +1,87 @@
|
|||
// From https://gist.github.com/186526/82b7372619fb4b029568040ee4a4aa44, Open source with MIT license
|
||||
|
||||
import { response, request } from "../index";
|
||||
import { defaultHeaders } from "../src/interface/response";
|
||||
import { response, request } from '../index';
|
||||
import { defaultHeaders } from '../src/interface/response';
|
||||
|
||||
function uuid(): string {
|
||||
const s: any[] = [];
|
||||
const hexDigits = "0123456789abcdef";
|
||||
const hexDigits = '0123456789abcdef';
|
||||
|
||||
for (let i = 0; i < 36; i++) {
|
||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
|
||||
}
|
||||
|
||||
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
|
||||
s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010
|
||||
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
|
||||
s[8] = s[13] = s[18] = s[23] = "-";
|
||||
s[8] = s[13] = s[18] = s[23] = '-';
|
||||
|
||||
const uuid = s.join("");
|
||||
const uuid = s.join('');
|
||||
|
||||
return uuid;
|
||||
}
|
||||
const codeAlternative:{[index: number]:string} = {
|
||||
100: "Continue",
|
||||
101: "Switching Protocols",
|
||||
102: "Processing",
|
||||
103: "Early Hints",
|
||||
200: "OK",
|
||||
201: "Created",
|
||||
202: "Accepted",
|
||||
203: "Non-Authoritative Information",
|
||||
204: "No Content",
|
||||
205: "Reset Content",
|
||||
206: "Partial Content",
|
||||
207: "Multi Status",
|
||||
208: "Already Reported",
|
||||
226: "IM Used",
|
||||
300: "Multiple Choices",
|
||||
301: "Moved Permanently",
|
||||
302: "Found",
|
||||
303: "See Other",
|
||||
304: "Not Modified",
|
||||
305: "Use Proxy",
|
||||
306: "Switch Proxy",
|
||||
307: "Temporary Redirect",
|
||||
308: "Permanent Redirect",
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
402: "Payment Required",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
406: "Not Acceptable",
|
||||
407: "Proxy Authentication Required",
|
||||
408: "Request Time-out",
|
||||
409: "Conflict",
|
||||
410: "Gone",
|
||||
411: "Length Required",
|
||||
412: "Precondition Failed",
|
||||
413: "Request Entity Too Large",
|
||||
414: "Request-URI Too Large",
|
||||
415: "Unsupported Media Type",
|
||||
416: "Requested Range not Satisfiable",
|
||||
417: "Expectation Failed",
|
||||
const codeAlternative: { [index: number]: string } = {
|
||||
100: 'Continue',
|
||||
101: 'Switching Protocols',
|
||||
102: 'Processing',
|
||||
103: 'Early Hints',
|
||||
200: 'OK',
|
||||
201: 'Created',
|
||||
202: 'Accepted',
|
||||
203: 'Non-Authoritative Information',
|
||||
204: 'No Content',
|
||||
205: 'Reset Content',
|
||||
206: 'Partial Content',
|
||||
207: 'Multi Status',
|
||||
208: 'Already Reported',
|
||||
226: 'IM Used',
|
||||
300: 'Multiple Choices',
|
||||
301: 'Moved Permanently',
|
||||
302: 'Found',
|
||||
303: 'See Other',
|
||||
304: 'Not Modified',
|
||||
305: 'Use Proxy',
|
||||
306: 'Switch Proxy',
|
||||
307: 'Temporary Redirect',
|
||||
308: 'Permanent Redirect',
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
406: 'Not Acceptable',
|
||||
407: 'Proxy Authentication Required',
|
||||
408: 'Request Time-out',
|
||||
409: 'Conflict',
|
||||
410: 'Gone',
|
||||
411: 'Length Required',
|
||||
412: 'Precondition Failed',
|
||||
413: 'Request Entity Too Large',
|
||||
414: 'Request-URI Too Large',
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Requested Range not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
418: "I'm a teapot",
|
||||
421: "Misdirected Request",
|
||||
422: "Unprocessable Entity",
|
||||
423: "Locked",
|
||||
424: "Failed Dependency",
|
||||
426: "Upgrade Required",
|
||||
428: "Precondition Required", // RFC 6585
|
||||
429: "Too Many Requests",
|
||||
431: "Request Header Fields Too Large", // RFC 6585
|
||||
451: "Unavailable For Legal Reasons",
|
||||
500: "Internal Server Error",
|
||||
501: "Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Gateway Time-out",
|
||||
505: "HTTP Version not Supported",
|
||||
506: "Variant Also Negotiates",
|
||||
507: "Insufficient Storage",
|
||||
508: "Loop Detected",
|
||||
510: "Not Extended",
|
||||
511: "Network Authentication Required",
|
||||
421: 'Misdirected Request',
|
||||
422: 'Unprocessable Entity',
|
||||
423: 'Locked',
|
||||
424: 'Failed Dependency',
|
||||
426: 'Upgrade Required',
|
||||
428: 'Precondition Required', // RFC 6585
|
||||
429: 'Too Many Requests',
|
||||
431: 'Request Header Fields Too Large', // RFC 6585
|
||||
451: 'Unavailable For Legal Reasons',
|
||||
500: 'Internal Server Error',
|
||||
501: 'Not Implemented',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
504: 'Gateway Time-out',
|
||||
505: 'HTTP Version not Supported',
|
||||
506: 'Variant Also Negotiates',
|
||||
507: 'Insufficient Storage',
|
||||
508: 'Loop Detected',
|
||||
510: 'Not Extended',
|
||||
511: 'Network Authentication Required',
|
||||
};
|
||||
|
||||
const template: string =
|
||||
|
@ -89,53 +89,53 @@ const template: string =
|
|||
|
||||
export default (
|
||||
errorCode: number,
|
||||
errorMessage: string = `The route you are trying access is error.`
|
||||
errorMessage: string = `The route you are trying access is error.`,
|
||||
) =>
|
||||
async (request: request<any>) => {
|
||||
return new response(
|
||||
template
|
||||
.replaceAll("${statusCode}", errorCode.toString())
|
||||
.replaceAll("${codeAlternative}", codeAlternative[errorCode])
|
||||
.replaceAll("${explain}", errorMessage)
|
||||
.replaceAll('${statusCode}', errorCode.toString())
|
||||
.replaceAll('${codeAlternative}', codeAlternative[errorCode])
|
||||
.replaceAll('${explain}', errorMessage)
|
||||
.replaceAll(
|
||||
"${howto}",
|
||||
`You can go to the <a href="/">homepage</a> of the website.`
|
||||
'${howto}',
|
||||
`You can go to the <a href="/">homepage</a> of the website.`,
|
||||
)
|
||||
.replaceAll("${id}", uuid())
|
||||
.replaceAll('${id}', uuid())
|
||||
.replaceAll(
|
||||
"${ServerStatus}",
|
||||
'${ServerStatus}',
|
||||
errorCode >= 500
|
||||
? "Error"
|
||||
? 'Error'
|
||||
: errorCode <= 500 && errorCode >= 400
|
||||
? `Working but ${codeAlternative[errorCode]}`
|
||||
: "Working"
|
||||
: 'Working',
|
||||
)
|
||||
.replaceAll(
|
||||
"${ServerStatusLowerCase}",
|
||||
'${ServerStatusLowerCase}',
|
||||
errorCode >= 500
|
||||
? "error"
|
||||
? 'error'
|
||||
: errorCode <= 500 && errorCode >= 400
|
||||
? `working-with-error`
|
||||
: "working"
|
||||
: 'working',
|
||||
)
|
||||
.replaceAll("${ip}", request.ip)
|
||||
.replaceAll('${ip}', request.ip)
|
||||
.replaceAll(
|
||||
"${clientStatus}",
|
||||
'${clientStatus}',
|
||||
errorCode <= 500 && errorCode >= 400
|
||||
? `Working but receive ${codeAlternative[errorCode]}`
|
||||
: "Working"
|
||||
: 'Working',
|
||||
)
|
||||
.replaceAll(
|
||||
"${clientStatusShort}",
|
||||
'${clientStatusShort}',
|
||||
errorCode <= 500 && errorCode >= 400
|
||||
? `working-with-error`
|
||||
: "working"
|
||||
: 'working',
|
||||
),
|
||||
errorCode,
|
||||
(() => {
|
||||
const Headers = new defaultHeaders();
|
||||
Headers.set("Content-Type", "text/html; charset=utf-8");
|
||||
Headers.set('Content-Type', 'text/html; charset=utf-8');
|
||||
return Headers;
|
||||
})()
|
||||
})(),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,73 +1,67 @@
|
|||
import {
|
||||
rootRouter,
|
||||
method,
|
||||
handler,
|
||||
route,
|
||||
response,
|
||||
} from "../index";
|
||||
import errorHandler from "./errorHandler";
|
||||
import { rootRouter, method, handler, route, response } from '../index';
|
||||
import errorHandler from './errorHandler';
|
||||
|
||||
const App = new rootRouter<any, any>();
|
||||
|
||||
App.binding(
|
||||
"/(.*)",
|
||||
new handler("ANY", [
|
||||
async (request, response) => {
|
||||
'/(.*)',
|
||||
new handler('ANY', [
|
||||
async (request, _response) => {
|
||||
console.log(request);
|
||||
return undefined;
|
||||
},
|
||||
])
|
||||
]),
|
||||
);
|
||||
|
||||
App.binding(
|
||||
"/",
|
||||
'/',
|
||||
App.create(
|
||||
"ANY",
|
||||
'ANY',
|
||||
(): Promise<string> =>
|
||||
new Promise(() => {
|
||||
console.log("Hello World!");
|
||||
throw new response("Hello World!");
|
||||
})
|
||||
)
|
||||
console.log('Hello World!');
|
||||
throw new response('Hello World!');
|
||||
}),
|
||||
),
|
||||
).binding(
|
||||
"/(.*)",
|
||||
'/(.*)',
|
||||
App.create(
|
||||
"ANY",
|
||||
'ANY',
|
||||
(): Promise<string> =>
|
||||
new Promise((resolve) => {
|
||||
resolve("Hello World?")
|
||||
})
|
||||
)
|
||||
resolve('Hello World?');
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
App.route("/v1/(.*)")
|
||||
App.route('/v1/(.*)')
|
||||
.add(
|
||||
new route(
|
||||
["/echo", "/echo/(.*)"],
|
||||
['/echo', '/echo/(.*)'],
|
||||
[
|
||||
new handler(method["GET"], [
|
||||
new handler(method['GET'], [
|
||||
async (requestMessage, responseMessage) => {
|
||||
responseMessage = responseMessage ?? new response("");
|
||||
responseMessage?.headers.set("Hello", "World");
|
||||
responseMessage = responseMessage ?? new response('');
|
||||
responseMessage?.headers.set('Hello', 'World');
|
||||
responseMessage.body = requestMessage.url.pathname;
|
||||
return responseMessage;
|
||||
},
|
||||
]),
|
||||
new handler(method["POST"], [
|
||||
new handler(method['POST'], [
|
||||
async (requestMessage, responseMessage) => {
|
||||
responseMessage = responseMessage ?? new response("");
|
||||
responseMessage = responseMessage ?? new response('');
|
||||
responseMessage.body = requestMessage.body;
|
||||
return responseMessage;
|
||||
},
|
||||
]),
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
.binding(
|
||||
"/error",
|
||||
App.create(method["ANY"], async () => {
|
||||
throw new Error("Nothing will happen here.");
|
||||
})
|
||||
'/error',
|
||||
App.create(method['ANY'], async () => {
|
||||
throw new Error('Nothing will happen here.');
|
||||
}),
|
||||
)
|
||||
.useErrorResponder(errorHandler);
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import globals from 'globals';
|
||||
import pluginJs from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default [
|
||||
{ files: ['src/**/*.{js,mjs,cjs,ts}', 'test/**/*.{js,mjs,cjs,ts}'] },
|
||||
{ languageOptions: { globals: globals.browser } },
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
args: 'all',
|
||||
argsIgnorePattern: '^_',
|
||||
caughtErrors: 'all',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
17
index.ts
17
index.ts
|
@ -1,12 +1,13 @@
|
|||
import { rootRouter } from "./src/router";
|
||||
import { rootRouter } from './src/router';
|
||||
|
||||
export { handler } from "./src/handler";
|
||||
export { route } from "./src/route";
|
||||
export { router } from "./src/router";
|
||||
export { methodENUM as method } from "./src/interface/method";
|
||||
export { response } from "./src/interface/response";
|
||||
export { request } from "./src/interface/request";
|
||||
export * as platformAdapater from "./src/platform/export";
|
||||
export { handler } from './src/handler';
|
||||
export { route } from './src/route';
|
||||
export { router } from './src/router';
|
||||
export { methodENUM as method } from './src/interface/method';
|
||||
export { response } from './src/interface/response';
|
||||
export { request } from './src/interface/request';
|
||||
export * as platformAdapater from './src/platform/export';
|
||||
export * as interfaces from './src/interface/index';
|
||||
|
||||
export { rootRouter };
|
||||
export default rootRouter;
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
export default {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
forceExit: true
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
// jest.config.ts
|
||||
import type { JestConfigWithTsJest } from 'ts-jest';
|
||||
|
||||
const jestConfig: JestConfigWithTsJest = {
|
||||
// [...]
|
||||
preset: 'ts-jest/presets/default-esm', // or other ESM presets
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
transform: {
|
||||
// '^.+\\.[tj]sx?$' to process ts,js,tsx,jsx with `ts-jest`
|
||||
// '^.+\\.m?[tj]sx?$' to process ts,js,tsx,jsx,mts,mjs,mtsx,mjsx with `ts-jest`
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default jestConfig;
|
46
package.json
46
package.json
|
@ -1,11 +1,17 @@
|
|||
{
|
||||
"name": "handlers.js",
|
||||
"description": "Handlers.js is a unified and lightweight web application framework for multiple platforms.",
|
||||
"version": "0.1.0",
|
||||
"main": "./dist/main.node.js",
|
||||
"webpack": "./dist/index.js",
|
||||
"browser": "./dist/main.serviceworker.js",
|
||||
"module": "./dist/index.js",
|
||||
"version": "0.1.2-1",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.node.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"LICENSE"
|
||||
|
@ -23,43 +29,53 @@
|
|||
"unified"
|
||||
],
|
||||
"dependencies": {
|
||||
"path-to-regexp": "^6.2.1"
|
||||
"path-to-regexp": "6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^3.13.0",
|
||||
"@eslint/js": "^9.8.0",
|
||||
"@types/jest": "^28.1.4",
|
||||
"@types/node": "^18.0.0",
|
||||
"@webpack-cli/generators": "^2.5.0",
|
||||
"axios": "^0.27.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"jest": "^28.1.2",
|
||||
"bun-types": "^0.1.4",
|
||||
"eslint": "9.x",
|
||||
"globals": "^15.8.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^28.0.5",
|
||||
"ts-jest": "^29.2.4",
|
||||
"ts-loader": "^9.3.1",
|
||||
"ts-node": "^10.8.1",
|
||||
"tsc-alias": "^1.8.10",
|
||||
"typescript": "^4.7.4",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "yarn clean && yarn build:node && yarn build:serviceworker && yarn build:cfworker && yarn build:deno && yarn build:txiki",
|
||||
"build": "yarn clean && yarn build:node && yarn build:serviceworker && yarn build:cfworker && yarn build:deno && yarn build:txiki && yarn build:bun",
|
||||
"build:node": "BUILD_TARGET=node webpack",
|
||||
"build:serviceworker": "BUILD_TARGET=serviceworker webpack",
|
||||
"build:cfworker": "BUILD_TARGET=cfworker webpack",
|
||||
"build:deno": "BUILD_TARGET=deno webpack",
|
||||
"build:txiki": "BUILD_TARGET=txiki webpack",
|
||||
"build:bun": "BUILD_TARGET=bun webpack",
|
||||
"watch": "webpack --watch",
|
||||
"clean": "rm -rf ./dist",
|
||||
"demo": "env NODE_ENV=development yarn build:node && node ./dist/main.node.js",
|
||||
"tsc": "tsc",
|
||||
"test:node": "jest ./test/node.test.ts",
|
||||
"tsc": "tsc && tsc-alias",
|
||||
"lint": "eslint --fix **/*.ts",
|
||||
"test:node": "NODE_OPTIONS=--experimental-vm-modules jest ./test/node.test.ts",
|
||||
"test:deno": "BUILD_TARGET=deno:test webpack && jest ./test/deno.test.ts",
|
||||
"coverage": "jest --collectCoverage --",
|
||||
"prepublish": "env NODE_ENV=production yarn build && yarn tsc"
|
||||
"coverage": "ODE_OPTIONS=--experimental-vm-modules jest --collectCoverage --",
|
||||
"prepublish": "env NODE_ENV=production yarn build && yarn tsc",
|
||||
"format": "prettier --write \"**/*.{ts,json,md}\" "
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.22"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { method, request, responder, response } from "./interface/index";
|
||||
import { method, request, responder, response } from './interface/index';
|
||||
|
||||
export class handler<RequestCustomType, ResponseCustomType> {
|
||||
public responders: responder<RequestCustomType, ResponseCustomType>[];
|
||||
|
@ -6,7 +6,7 @@ export class handler<RequestCustomType, ResponseCustomType> {
|
|||
|
||||
constructor(
|
||||
method: method,
|
||||
responders: responder<RequestCustomType, ResponseCustomType>[]
|
||||
responders: responder<RequestCustomType, ResponseCustomType>[],
|
||||
) {
|
||||
this.responders = responders;
|
||||
this.method = method;
|
||||
|
@ -19,22 +19,26 @@ export class handler<RequestCustomType, ResponseCustomType> {
|
|||
async respond(
|
||||
request: request<RequestCustomType>,
|
||||
responseMessage: response<ResponseCustomType> = new response<ResponseCustomType>(
|
||||
""
|
||||
)
|
||||
'',
|
||||
),
|
||||
): Promise<response<ResponseCustomType> | void> {
|
||||
switch (this.responders.length) {
|
||||
case 0:
|
||||
Promise.reject("No responders found in this handler.");
|
||||
Promise.reject('No responders found in this handler.');
|
||||
break;
|
||||
case 1:
|
||||
return this.responders[0](request, responseMessage);
|
||||
default:
|
||||
for (let responder of this.responders) {
|
||||
let thisResponse = await responder(request, responseMessage);
|
||||
for (const responder of this.responders) {
|
||||
const thisResponse = await responder(
|
||||
request,
|
||||
responseMessage,
|
||||
);
|
||||
if (thisResponse instanceof response) {
|
||||
responseMessage = thisResponse;
|
||||
}
|
||||
}
|
||||
return responseMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as lib from "../lib";
|
||||
import * as lib from '../lib';
|
||||
export class headers {
|
||||
public headers: { [key: string]: string } = {};
|
||||
constructor(headers: { [key: string]: string }) {
|
||||
|
@ -25,7 +25,7 @@ export class headers {
|
|||
forEach(func: (key: string, value: string) => any) {
|
||||
Object.keys(this.headers).forEach((key) => {
|
||||
func(key, this.headers[key]);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
export default headers;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export { request } from "./request";
|
||||
export { response } from "./response";
|
||||
export { method } from "./method";
|
||||
export { headers } from "./headers";
|
||||
export { responder } from "./responder";
|
||||
export const AllMismatchInterrupted = new Error("AllMismatchInterrupted");
|
||||
export { request } from './request';
|
||||
export { response } from './response';
|
||||
export { method } from './method';
|
||||
export { headers } from './headers';
|
||||
export { responder } from './responder';
|
||||
export const AllMismatchInterrupted = new Error('AllMismatchInterrupted');
|
||||
export type path = string | RegExp;
|
||||
|
|
|
@ -3,59 +3,70 @@ export enum methodENUM {
|
|||
* The `CONNECT` method establishes a tunnel to the server identified by the
|
||||
* target resource.
|
||||
*/
|
||||
CONNECT = "CONNECT",
|
||||
CONNECT = 'CONNECT',
|
||||
|
||||
/**
|
||||
* The `DELETE` method deletes the specified resource.
|
||||
*/
|
||||
DELETE = "DELETE",
|
||||
DELETE = 'DELETE',
|
||||
|
||||
/**
|
||||
* The `GET` method requests a representation of the specified resource.
|
||||
* Requests using GET should only retrieve data.
|
||||
*/
|
||||
GET = "GET",
|
||||
GET = 'GET',
|
||||
|
||||
/**
|
||||
* The `HEAD` method asks for a response identical to that of a GET request,
|
||||
* but without the response body.
|
||||
*/
|
||||
HEAD = "HEAD",
|
||||
HEAD = 'HEAD',
|
||||
|
||||
/**
|
||||
* The `OPTIONS` method is used to describe the communication options for the
|
||||
* target resource.
|
||||
*/
|
||||
OPTIONS = "OPTIONS",
|
||||
OPTIONS = 'OPTIONS',
|
||||
|
||||
/**
|
||||
* The PATCH method is used to apply partial modifications to a resource.
|
||||
*/
|
||||
PATCH = "PATCH",
|
||||
PATCH = 'PATCH',
|
||||
|
||||
/**
|
||||
* The `POST` method is used to submit an entity to the specified resource,
|
||||
* often causing a change in state or side effects on the server.
|
||||
*/
|
||||
POST = "POST",
|
||||
POST = 'POST',
|
||||
|
||||
/**
|
||||
* The `PUT` method replaces all current representations of the target
|
||||
* resource with the request payload.
|
||||
*/
|
||||
PUT = "PUT",
|
||||
PUT = 'PUT',
|
||||
|
||||
/**
|
||||
* The `TRACE` method performs a message loop-back test along the path to the
|
||||
* target resource.
|
||||
*/
|
||||
TRACE = "TRACE",
|
||||
TRACE = 'TRACE',
|
||||
/**
|
||||
* The `ANY` method will match any method.
|
||||
*/
|
||||
ANY = "ANY",
|
||||
ANY = 'ANY',
|
||||
}
|
||||
|
||||
export type method = "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE" | "ANY" | methodENUM;
|
||||
export type method =
|
||||
| 'CONNECT'
|
||||
| 'DELETE'
|
||||
| 'GET'
|
||||
| 'HEAD'
|
||||
| 'OPTIONS'
|
||||
| 'PATCH'
|
||||
| 'POST'
|
||||
| 'PUT'
|
||||
| 'TRACE'
|
||||
| 'ANY'
|
||||
| methodENUM;
|
||||
|
||||
export default method;
|
|
@ -1,5 +1,5 @@
|
|||
import method from "./method";
|
||||
import headers from "./headers";
|
||||
import method from './method';
|
||||
import headers from './headers';
|
||||
export class request<RequestCustomType> {
|
||||
public readonly method: method;
|
||||
public readonly url: URL;
|
||||
|
@ -16,7 +16,7 @@ export class request<RequestCustomType> {
|
|||
headers: headers,
|
||||
body: any,
|
||||
params: { [key: string]: string },
|
||||
ip: string = "0.0.0.0"
|
||||
ip: string = '0.0.0.0',
|
||||
) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
|
@ -24,7 +24,10 @@ export class request<RequestCustomType> {
|
|||
this.body = body;
|
||||
this.query = new URLSearchParams(url.search);
|
||||
this.params = params;
|
||||
this.ip = headers.get("X-REAL-IP") ?? headers.get("X-Forwarded-For")?.split(" ")[0] ?? ip;
|
||||
this.ip =
|
||||
headers.get('X-REAL-IP') ??
|
||||
headers.get('X-Forwarded-For')?.split(' ')[0] ??
|
||||
ip;
|
||||
}
|
||||
public extends(custom: RequestCustomType): request<RequestCustomType> {
|
||||
this.custom = custom;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { request, response } from "./index";
|
||||
import { request, response } from './index';
|
||||
export interface responder<RequestCustomType, ResponseCustomType> {
|
||||
(
|
||||
request: request<RequestCustomType>,
|
||||
reponse?: response<ResponseCustomType>
|
||||
reponse?: response<ResponseCustomType>,
|
||||
): Promise<response<ResponseCustomType>> | Promise<void> | void;
|
||||
}
|
||||
export default responder;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import headers from "./headers";
|
||||
import packageJSON from "../../package.json";
|
||||
import { platform, version } from "../lib";
|
||||
import headers from './headers';
|
||||
import packageJSON from '../../package.json' assert { type: 'json' };
|
||||
import { platform, version } from '../lib';
|
||||
|
||||
export class defaultHeaders extends headers {
|
||||
constructor(headers: { [key: string]: string } = {}) {
|
||||
super(headers);
|
||||
if (!this.has("Content-Type"))
|
||||
this.set("Content-Type", "text/plain; charset=utf-8");
|
||||
if (!this.has('Content-Type'))
|
||||
this.set('Content-Type', 'text/plain; charset=utf-8');
|
||||
this.set(
|
||||
"Server",
|
||||
`Handlers.js/${packageJSON.version} ${platform}/${version}`
|
||||
'Server',
|
||||
`Handlers.js/${packageJSON.version} ${platform}/${version}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export class response<ResponseCustomType> {
|
|||
public constructor(
|
||||
body: any,
|
||||
status: number = 200,
|
||||
headers: headers = new defaultHeaders()
|
||||
headers: headers = new defaultHeaders(),
|
||||
) {
|
||||
this.status = status;
|
||||
this.headers = headers;
|
||||
|
|
31
src/lib.ts
31
src/lib.ts
|
@ -1,29 +1,34 @@
|
|||
export const firstUpperCase = ([first, ...rest]: string) =>
|
||||
first?.toUpperCase() + rest.map((e) => e.toLowerCase()).join("");
|
||||
first?.toUpperCase() + rest.map((e) => e.toLowerCase()).join('');
|
||||
export const platform = (() => {
|
||||
if (typeof process != "undefined") {
|
||||
return "Node.js";
|
||||
if (typeof process != 'undefined') {
|
||||
return 'Node.js';
|
||||
}
|
||||
if (typeof Deno != "undefined") {
|
||||
return "Deno";
|
||||
if (typeof Deno != 'undefined') {
|
||||
return 'Deno';
|
||||
}
|
||||
if (typeof tjs != "undefined") {
|
||||
return "txiki.js";
|
||||
if (typeof Bun != 'undefined') {
|
||||
return 'Bun';
|
||||
}
|
||||
if (typeof self != "undefined") {
|
||||
return "Service Worker";
|
||||
if (typeof tjs != 'undefined') {
|
||||
return 'txiki.js';
|
||||
}
|
||||
if (typeof self != 'undefined') {
|
||||
return 'Service Worker';
|
||||
}
|
||||
return undefined;
|
||||
})();
|
||||
export const version = (() => {
|
||||
switch (platform) {
|
||||
case "Node.js":
|
||||
case 'Node.js':
|
||||
return process.version;
|
||||
case "Deno":
|
||||
case 'Bun':
|
||||
return process.version;
|
||||
case 'Deno':
|
||||
return Deno.version.deno;
|
||||
case "txiki.js":
|
||||
case 'txiki.js':
|
||||
return tjs.versions.tjs;
|
||||
case "Service Worker":
|
||||
case 'Service Worker':
|
||||
return undefined;
|
||||
default:
|
||||
return undefined;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { SWPlatformAdapter } from './serviceworker';
|
||||
import { platformAdapater } from './index';
|
||||
|
||||
export class BunPlatformAdapter<T = any, K = any>
|
||||
extends SWPlatformAdapter<T, K>
|
||||
implements platformAdapater<T, K>
|
||||
{
|
||||
async listen(port: number): Promise<void> {
|
||||
Bun.serve({
|
||||
fetch: async (request: Request): Promise<Response> => {
|
||||
return await this.handleResponse(
|
||||
await this.handleRequest(request).then((request) =>
|
||||
this.router.respond(request),
|
||||
),
|
||||
);
|
||||
},
|
||||
port,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,28 +1,27 @@
|
|||
import { SWPlatformAdapter } from "./serviceworker";
|
||||
import { platformAdapater } from "./index";
|
||||
|
||||
import { request } from "../interface/request";
|
||||
import { headers } from "../interface/headers";
|
||||
import { methodENUM } from "src/interface/method";
|
||||
import { SWPlatformAdapter } from './serviceworker';
|
||||
import { platformAdapater } from './index';
|
||||
|
||||
import { request } from '../interface/request';
|
||||
import { headers } from '../interface/headers';
|
||||
import { methodENUM } from 'src/interface/method';
|
||||
|
||||
const DefaultConn: Deno.Conn = {
|
||||
localAddr: {
|
||||
transport: "tcp",
|
||||
hostname: "0.0.0.0",
|
||||
transport: 'tcp',
|
||||
hostname: '0.0.0.0',
|
||||
port: 80,
|
||||
},
|
||||
remoteAddr: {
|
||||
transport: "tcp",
|
||||
hostname: "0.0.0.0",
|
||||
transport: 'tcp',
|
||||
hostname: '0.0.0.0',
|
||||
port: 80,
|
||||
},
|
||||
rid: 0,
|
||||
closeWrite: async () => undefined,
|
||||
readable: "",
|
||||
writable: "",
|
||||
read: async (p: Uint8Array) => null,
|
||||
write: async (p: Uint8Array) => 0,
|
||||
readable: '',
|
||||
writable: '',
|
||||
read: async (_p: Uint8Array) => null,
|
||||
write: async (_p: Uint8Array) => 0,
|
||||
close: () => undefined,
|
||||
};
|
||||
|
||||
|
@ -30,21 +29,32 @@ export class DenoPlatformAdapter<T = any, K = any>
|
|||
extends SWPlatformAdapter<T, K>
|
||||
implements platformAdapater<T, K>
|
||||
{
|
||||
async listen(port: number): Promise<void> {
|
||||
const Server: Deno.Listener = Deno.listen({ port });
|
||||
public server: Deno.Listener;
|
||||
|
||||
for await (const connection of Server) {
|
||||
async listen(port: number): Promise<void> {
|
||||
this.server = Deno.listen({ port });
|
||||
|
||||
for await (const connection of this.server) {
|
||||
const httpConnection = Deno.serveHttp(connection);
|
||||
|
||||
for await (const requestEvent of httpConnection) {
|
||||
requestEvent.respondWith(this.handler(requestEvent, connection));
|
||||
requestEvent.respondWith(
|
||||
this.handler(requestEvent, connection),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handleRequest(nativeRequest: Request, connection: Deno.Conn = DefaultConn): Promise<request<T>> {
|
||||
close() {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
async handleRequest(
|
||||
nativeRequest: Request,
|
||||
connection: Deno.Conn = DefaultConn,
|
||||
): Promise<request<T>> {
|
||||
const requestHeaders = new headers(
|
||||
Object.fromEntries(nativeRequest.headers.entries())
|
||||
Object.fromEntries(nativeRequest.headers.entries()),
|
||||
);
|
||||
const requestMessage: request<T> = new request(
|
||||
<methodENUM>nativeRequest.method,
|
||||
|
@ -52,16 +62,21 @@ export class DenoPlatformAdapter<T = any, K = any>
|
|||
requestHeaders,
|
||||
await nativeRequest.text(),
|
||||
{},
|
||||
`${connection.remoteAddr.hostname}:${connection.remoteAddr.port}` || ""
|
||||
`${connection.remoteAddr.hostname ?? '0.0.0.0'}:${
|
||||
connection.remoteAddr.port ?? '0'
|
||||
}`,
|
||||
);
|
||||
return requestMessage;
|
||||
}
|
||||
|
||||
async handler(event: FetchEvent, connection: Deno.Conn = DefaultConn): Promise<Response> {
|
||||
async handler(
|
||||
event: FetchEvent,
|
||||
connection: Deno.Conn = DefaultConn,
|
||||
): Promise<Response> {
|
||||
return await this.handleResponse(
|
||||
await this.handleRequest(event.request, connection).then((request) =>
|
||||
this.router.respond(request)
|
||||
)
|
||||
)
|
||||
await this.handleRequest(event.request, connection).then(
|
||||
(request) => this.router.respond(request),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import { NodePlatformAdapter } from "./node";
|
||||
import { SWPlatformAdapter } from "./serviceworker";
|
||||
import { DenoPlatformAdapter } from "./deno";
|
||||
import { TxikiPlatformAdapter } from "./txiki";
|
||||
import { NodePlatformAdapter } from './node';
|
||||
import { SWPlatformAdapter } from './serviceworker';
|
||||
import { DenoPlatformAdapter } from './deno';
|
||||
import { TxikiPlatformAdapter } from './txiki';
|
||||
|
||||
export const platformAdapaterMapping = {
|
||||
"Node.js": NodePlatformAdapter,
|
||||
"Service Worker": SWPlatformAdapter,
|
||||
"Deno": DenoPlatformAdapter,
|
||||
"txiki.js": TxikiPlatformAdapter,
|
||||
'Node.js': NodePlatformAdapter,
|
||||
'Service Worker': SWPlatformAdapter,
|
||||
Deno: DenoPlatformAdapter,
|
||||
'txiki.js': TxikiPlatformAdapter,
|
||||
};
|
||||
|
||||
export { NodePlatformAdapter, SWPlatformAdapter, DenoPlatformAdapter, TxikiPlatformAdapter };
|
||||
export {
|
||||
NodePlatformAdapter,
|
||||
SWPlatformAdapter,
|
||||
DenoPlatformAdapter,
|
||||
TxikiPlatformAdapter,
|
||||
};
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { request, response } from "../interface/index";
|
||||
import { router } from "../router";
|
||||
import { request, response } from '../interface/index';
|
||||
import { router } from '../router';
|
||||
|
||||
export interface platformAdapater<T = any, K = any> {
|
||||
router: router<T, K>;
|
||||
listen(port: number): void;
|
||||
close(): void;
|
||||
handleRequest(nativeRequest: any): Promise<request<T>>;
|
||||
handleResponse(response: response<K> | Promise<response<K>>, nativeResponse?: any): any;
|
||||
handleResponse(
|
||||
response: response<K> | Promise<response<K>>,
|
||||
nativeResponse?: any,
|
||||
): any;
|
||||
}
|
||||
|
||||
export interface platformAdapaterConstructor<T = any, K = any> {
|
||||
|
@ -14,7 +18,7 @@ export interface platformAdapaterConstructor<T = any, K = any> {
|
|||
|
||||
export function createPlatformAdapater(
|
||||
adapater: platformAdapaterConstructor,
|
||||
router: router
|
||||
router: router,
|
||||
): platformAdapater {
|
||||
return new adapater(router);
|
||||
}
|
||||
|
|
|
@ -1,56 +1,65 @@
|
|||
import { platformAdapater } from "./index";
|
||||
import { request, response } from "../interface/index";
|
||||
import { router } from "../router";
|
||||
import { headers } from "../interface/headers";
|
||||
import { platformAdapater } from './index';
|
||||
import { request, response } from '../interface/index';
|
||||
import { router } from '../router';
|
||||
import { headers } from '../interface/headers';
|
||||
|
||||
import http from "http";
|
||||
import { methodENUM } from "src/interface/method";
|
||||
import http from 'http';
|
||||
import { methodENUM } from 'src/interface/method';
|
||||
|
||||
export class NodePlatformAdapter<T = any, K = any> implements platformAdapater {
|
||||
public router: router<T, K>;
|
||||
public server: http.Server;
|
||||
|
||||
constructor(router: router<T, K>) {
|
||||
constructor(router: router<T, K>, server?: http.Server) {
|
||||
this.router = router;
|
||||
if (server) this.server = server;
|
||||
else this.server = http.createServer();
|
||||
}
|
||||
|
||||
async listen(port: number): Promise<void> {
|
||||
const server = http.createServer();
|
||||
server.on(
|
||||
"request",
|
||||
this.server.on(
|
||||
'request',
|
||||
async (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
const request = await this.handleRequest(req);
|
||||
const response = await this.router.respond(request);
|
||||
this.handleResponse(response, res);
|
||||
}
|
||||
},
|
||||
);
|
||||
server.listen(port);
|
||||
return;
|
||||
this.server.listen(port);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
async handleRequest(
|
||||
nativeRequest: http.IncomingMessage
|
||||
nativeRequest: http.IncomingMessage,
|
||||
): Promise<request<T>> {
|
||||
if (
|
||||
typeof nativeRequest.method != "string" ||
|
||||
typeof nativeRequest.url != "string" ||
|
||||
typeof nativeRequest.headers != "object"
|
||||
typeof nativeRequest.method != 'string' ||
|
||||
typeof nativeRequest.url != 'string' ||
|
||||
typeof nativeRequest.headers != 'object'
|
||||
) {
|
||||
throw new Error("Invalid request");
|
||||
throw new Error('Invalid request');
|
||||
}
|
||||
|
||||
let body: string = "";
|
||||
const ip: string = nativeRequest.socket.remoteAddress?.replace("::ffff:", "") ?? "0.0.0.0";
|
||||
let body: string = '';
|
||||
const ip: string =
|
||||
nativeRequest.socket.remoteAddress?.replace('::ffff:', '') ??
|
||||
'0.0.0.0';
|
||||
const requestHeaders = new headers(<any>nativeRequest.headers);
|
||||
|
||||
if (!["GET", "HEAD", "DELETE", "OPTIONS"].includes(nativeRequest.method)) {
|
||||
nativeRequest.on("data", (data: string) => {
|
||||
if (
|
||||
!['GET', 'HEAD', 'DELETE', 'OPTIONS'].includes(nativeRequest.method)
|
||||
) {
|
||||
nativeRequest.on('data', (data: string) => {
|
||||
body += data;
|
||||
});
|
||||
|
||||
await new Promise((resolve) =>
|
||||
nativeRequest.on("end", () => {
|
||||
nativeRequest.on('end', () => {
|
||||
resolve(true);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -58,12 +67,12 @@ export class NodePlatformAdapter<T = any, K = any> implements platformAdapater {
|
|||
<methodENUM>nativeRequest.method,
|
||||
new URL(
|
||||
nativeRequest.url,
|
||||
`http://${requestHeaders.get("host") ?? "localhost"}`
|
||||
`http://${requestHeaders.get('host') ?? 'localhost'}`,
|
||||
),
|
||||
requestHeaders,
|
||||
body,
|
||||
{},
|
||||
ip
|
||||
ip,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,32 @@
|
|||
import { platformAdapater } from "./index";
|
||||
import { request } from "../interface/request";
|
||||
import { response } from "../interface/response";
|
||||
import { router } from "../router";
|
||||
import { headers } from "../interface/headers";
|
||||
import { platformAdapater } from './index';
|
||||
import { request } from '../interface/request';
|
||||
import { response } from '../interface/response';
|
||||
import { router } from '../router';
|
||||
import { headers } from '../interface/headers';
|
||||
|
||||
import { methodENUM } from "src/interface/method";
|
||||
import { methodENUM } from 'src/interface/method';
|
||||
|
||||
export class SWPlatformAdapter<T = any, K = any> implements platformAdapater {
|
||||
public router: router<T, K>;
|
||||
private eventHandler = (event: FetchEvent) => {
|
||||
event.respondWith(this.handler(event));
|
||||
};
|
||||
|
||||
constructor(router: router<T, K>) {
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
async listen(_port?: number): Promise<void> {
|
||||
self.addEventListener("fetch", (event: FetchEvent) => {
|
||||
event.respondWith(this.handler(event));
|
||||
});
|
||||
self.addEventListener('fetch', this.eventHandler);
|
||||
}
|
||||
|
||||
close() {
|
||||
self.removeEventListener('fetch', this.eventHandler);
|
||||
}
|
||||
|
||||
async handleRequest(nativeRequest: Request): Promise<request<T>> {
|
||||
const requestHeaders = new headers(
|
||||
Object.fromEntries(nativeRequest.headers.entries())
|
||||
Object.fromEntries(nativeRequest.headers.entries()),
|
||||
);
|
||||
const requestMessage: request<T> = new request(
|
||||
<methodENUM>nativeRequest.method,
|
||||
|
@ -29,13 +34,15 @@ export class SWPlatformAdapter<T = any, K = any> implements platformAdapater {
|
|||
requestHeaders,
|
||||
await nativeRequest.text(),
|
||||
{},
|
||||
requestHeaders.get("CF-Connecting-IP") || ""
|
||||
requestHeaders.get('CF-Connecting-IP') || '',
|
||||
);
|
||||
return requestMessage;
|
||||
}
|
||||
|
||||
async handleResponse(response: response<K>): Promise<Response> {
|
||||
if (response.status === 204) { response.body = null; }
|
||||
if (response.status === 204) {
|
||||
response.body = null;
|
||||
}
|
||||
const nativResponse = new Response(response.body, {
|
||||
status: response.status,
|
||||
headers: response.headers.headers,
|
||||
|
@ -46,8 +53,8 @@ export class SWPlatformAdapter<T = any, K = any> implements platformAdapater {
|
|||
async handler(event: FetchEvent): Promise<Response> {
|
||||
return await this.handleResponse(
|
||||
await this.handleRequest(event.request).then((request) =>
|
||||
this.router.respond(request)
|
||||
)
|
||||
)
|
||||
this.router.respond(request),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { request } from "../../interface/request";
|
||||
import { response } from "../../interface";
|
||||
import { headers } from "../../interface/headers";
|
||||
import { methodENUM } from "../../interface/method";
|
||||
import { request } from '../../interface/request';
|
||||
import { response } from '../../interface';
|
||||
import { headers } from '../../interface/headers';
|
||||
import { methodENUM } from '../../interface/method';
|
||||
|
||||
import statusCode from "./statusCode.json";
|
||||
import statusCode from './statusCode.json' assert { type: 'json' };
|
||||
|
||||
export class HttpConn {
|
||||
private closed: boolean = false;
|
||||
|
@ -16,44 +16,65 @@ export class HttpConn {
|
|||
}
|
||||
|
||||
private readMessage(httpMessage: string): request<any> {
|
||||
const lines = httpMessage.split("\n");
|
||||
const lines = httpMessage.split('\n');
|
||||
const firstLine = lines[0];
|
||||
const dividingIndex = lines.indexOf("\r") ?? lines.indexOf("");
|
||||
const dividingIndex = lines.indexOf('\r') ?? lines.indexOf('');
|
||||
const rawHeaders = lines.slice(1, dividingIndex);
|
||||
|
||||
const [method, path, version] = firstLine.split(" ");
|
||||
const [method, path, version] = firstLine.split(' ');
|
||||
|
||||
if (!(version in ['HTTP/1.1', 'HTTP/1.0', 'HTTP/0.9'])) {
|
||||
this.conn.close();
|
||||
}
|
||||
|
||||
const requestHeaders = new headers({});
|
||||
for (const header of rawHeaders) {
|
||||
const [key, value] = header.split(": ");
|
||||
const [key, value] = header.split(': ');
|
||||
requestHeaders.set(key, value);
|
||||
}
|
||||
|
||||
const url = new URL(path, `http://${requestHeaders.get("Host")}/` ?? `http://${this.conn.localAddress.ip}:${this.conn.localAddress.port}/`);
|
||||
const url = new URL(
|
||||
path,
|
||||
`http://${
|
||||
requestHeaders.get('Host') ??
|
||||
`${this.conn.localAddress.ip}:${this.conn.localAddress.port}`
|
||||
}/`,
|
||||
);
|
||||
|
||||
const body = lines.slice(dividingIndex + 1).join("\n");
|
||||
const body = lines.slice(dividingIndex + 1).join('\n');
|
||||
|
||||
const requestMessage = new request<any>(<methodENUM>method, url, requestHeaders, body, {}, this.conn.remoteAddress.ip);
|
||||
const requestMessage = new request<any>(
|
||||
<methodENUM>method,
|
||||
url,
|
||||
requestHeaders,
|
||||
body,
|
||||
{},
|
||||
this.conn.remoteAddress.ip,
|
||||
);
|
||||
return requestMessage;
|
||||
}
|
||||
|
||||
private handleResponse(response: response<any>) {
|
||||
let responseMessage: string = "";
|
||||
responseMessage += "HTTP/1.1 " + response.status + " " + statusCode[<"100">response.status.toString()] ?? "";
|
||||
let responseMessage: string = '';
|
||||
responseMessage +=
|
||||
'HTTP/1.1 ' +
|
||||
response.status +
|
||||
' ' +
|
||||
(statusCode[<'100'>response.status.toString()] ?? '');
|
||||
|
||||
response.headers.forEach((key, value) => {
|
||||
responseMessage += "\n" + key + ": " + value;
|
||||
responseMessage += '\n' + key + ': ' + value;
|
||||
});
|
||||
|
||||
responseMessage += "\n\n" + response.body;
|
||||
responseMessage += '\n\n' + response.body;
|
||||
|
||||
this.conn.write(new TextEncoder().encode(responseMessage));
|
||||
this.conn.shutdown();
|
||||
this.conn.close();
|
||||
this.closed = true;
|
||||
}
|
||||
|
||||
private async read(): Promise<request<any> | undefined> {
|
||||
let message = "";
|
||||
let message = '';
|
||||
const { done, value } = await this.reader.read();
|
||||
|
||||
if (done || this.closed) {
|
||||
|
@ -61,15 +82,22 @@ export class HttpConn {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
message += String.fromCharCode(...Object.values(<{
|
||||
[key: string]: number
|
||||
}>value));
|
||||
message += String.fromCharCode(
|
||||
...Object.values(
|
||||
<
|
||||
{
|
||||
[key: string]: number;
|
||||
}
|
||||
>value,
|
||||
),
|
||||
);
|
||||
|
||||
const requestMessage = this.readMessage(message);
|
||||
return requestMessage;
|
||||
}
|
||||
|
||||
[Symbol.asyncIterator]() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const httpConn = this;
|
||||
|
||||
return {
|
||||
|
@ -82,10 +110,10 @@ export class HttpConn {
|
|||
respondWith: (response: response<any>) => {
|
||||
httpConn.handleResponse(response);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
import { platformAdapater } from "./index";
|
||||
import { request } from "../interface/request";
|
||||
import { response } from "../interface/response";
|
||||
import { router } from "../router";
|
||||
import serveHttp from "./txiki-js/serveHttp";
|
||||
import { platformAdapater } from './index';
|
||||
import { request } from '../interface/request';
|
||||
import { response } from '../interface/response';
|
||||
import { router } from '../router';
|
||||
import serveHttp from './txiki-js/serveHttp';
|
||||
|
||||
export class TxikiPlatformAdapter<T = any, K = any> implements platformAdapater {
|
||||
export class TxikiPlatformAdapter<T = any, K = any>
|
||||
implements platformAdapater
|
||||
{
|
||||
public router: router<T, K>;
|
||||
public server: tjs.Listener;
|
||||
|
||||
constructor(router: router<T, K>) {
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
async listen(port?: number): Promise<void> {
|
||||
const Server = await tjs.listen("tcp", "0.0.0.0", port);
|
||||
this.server = await tjs.listen('tcp', '0.0.0.0', port);
|
||||
|
||||
for await (const conn of Server) {
|
||||
for await (const conn of this.server) {
|
||||
const httpConn = serveHttp(conn);
|
||||
|
||||
for await (const conn of httpConn) {
|
||||
if (typeof conn == "undefined" || typeof conn.request == "undefined") {
|
||||
if (
|
||||
typeof conn == 'undefined' ||
|
||||
typeof conn.request == 'undefined'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
conn.respondWith(await this.router.respond(conn.request));
|
||||
|
@ -26,6 +32,10 @@ export class TxikiPlatformAdapter<T = any, K = any> implements platformAdapater
|
|||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.server.close();
|
||||
}
|
||||
|
||||
async handleRequest(nativeRequest: request<any>): Promise<request<T>> {
|
||||
return nativeRequest;
|
||||
}
|
||||
|
|
19
src/route.ts
19
src/route.ts
|
@ -1,6 +1,6 @@
|
|||
import { path } from "./interface";
|
||||
import handler from "./handler";
|
||||
import { pathToRegexp } from "path-to-regexp";
|
||||
import { path } from './interface';
|
||||
import handler from './handler';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
|
||||
interface matchedStatus {
|
||||
matched: boolean;
|
||||
|
@ -16,26 +16,25 @@ interface regExpKey {
|
|||
suffix: string;
|
||||
pattern: string;
|
||||
modifier: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class route {
|
||||
private paths: path[];
|
||||
public handlers: handler<any, any>[];
|
||||
private regExps: { regExp: RegExp, keys: regExpKey[] }[] = [];
|
||||
private regExps: { regExp: RegExp; keys: regExpKey[] }[] = [];
|
||||
|
||||
constructor(paths: path[], handlers: handler<any, any>[]) {
|
||||
this.paths = paths;
|
||||
this.handlers = handlers;
|
||||
|
||||
this.paths.forEach(path => {
|
||||
this.paths.forEach((path) => {
|
||||
const keys: regExpKey[] = [];
|
||||
this.regExps.push({ regExp: pathToRegexp(path, keys), keys });
|
||||
})
|
||||
});
|
||||
}
|
||||
async exec(path: string): Promise<matchedStatus> {
|
||||
let Answer = await Promise.all<Promise<matchedStatus>>(
|
||||
this.regExps.map(async (it) => {
|
||||
|
||||
const answer = it.regExp.exec(path);
|
||||
if (answer === null)
|
||||
return {
|
||||
|
@ -43,7 +42,7 @@ export class route {
|
|||
attributes: [],
|
||||
};
|
||||
|
||||
let attributes: matchedStatus["attributes"] = [];
|
||||
const attributes: matchedStatus['attributes'] = [];
|
||||
|
||||
it.keys.forEach((key, index) => {
|
||||
attributes.push({
|
||||
|
@ -56,7 +55,7 @@ export class route {
|
|||
matched: true,
|
||||
attributes: attributes,
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
Answer = Answer.filter((it) => it.matched);
|
||||
if (Answer.length === 0)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import handler from "./handler";
|
||||
import handler from './handler';
|
||||
import {
|
||||
path,
|
||||
response,
|
||||
|
@ -6,24 +6,24 @@ import {
|
|||
AllMismatchInterrupted,
|
||||
responder,
|
||||
method,
|
||||
} from "./interface/index";
|
||||
import { defaultHeaders } from "./interface/response";
|
||||
import route from "./route";
|
||||
import { methodENUM } from "./interface/method";
|
||||
} from './interface/index';
|
||||
import { defaultHeaders } from './interface/response';
|
||||
import route from './route';
|
||||
import { methodENUM } from './interface/method';
|
||||
import {
|
||||
createPlatformAdapater,
|
||||
platformAdapaterConstructor,
|
||||
platformAdapater,
|
||||
} from "./platform/index";
|
||||
import { platformAdapaterMapping } from "./platform/export";
|
||||
import { platform } from "./lib";
|
||||
} from './platform/index';
|
||||
import { platformAdapaterMapping } from './platform/export';
|
||||
import { platform } from './lib';
|
||||
|
||||
export class router<K = any, V = any> {
|
||||
public routes: route[];
|
||||
|
||||
public errorResponder: (
|
||||
errorCode: number,
|
||||
errorMessage?: string
|
||||
errorMessage?: string,
|
||||
) => responder<K, V>;
|
||||
|
||||
constructor(routes: route[] = []) {
|
||||
|
@ -43,33 +43,33 @@ export class router<K = any, V = any> {
|
|||
create(
|
||||
method: method,
|
||||
responder: (
|
||||
request: request<K>
|
||||
request: request<K>,
|
||||
) =>
|
||||
| Promise<response<V>>
|
||||
| Promise<string>
|
||||
| Promise<object>
|
||||
| Promise<number>
|
||||
| Promise<void>
|
||||
| Promise<void>,
|
||||
) {
|
||||
return new handler<K, V>(method, [
|
||||
async (request: request<K>) => {
|
||||
const answer = await responder(request);
|
||||
if (answer instanceof response) {
|
||||
return answer;
|
||||
} else if (typeof answer == "string") {
|
||||
} else if (typeof answer == 'string') {
|
||||
return new response(answer);
|
||||
} else if (typeof answer == "number") {
|
||||
} else if (typeof answer == 'number') {
|
||||
return new response(answer.toString());
|
||||
} else if (typeof answer == "object") {
|
||||
} else if (typeof answer == 'object') {
|
||||
return new response(
|
||||
JSON.stringify(answer),
|
||||
200,
|
||||
new defaultHeaders({
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
})
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return new response("", 204);
|
||||
return new response('', 204);
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
@ -89,16 +89,16 @@ export class router<K = any, V = any> {
|
|||
|
||||
async _respond(
|
||||
request: request<K>,
|
||||
responseMessage: response<V> = new response<V>("")
|
||||
responseMessage: response<V> = new response<V>(''),
|
||||
): Promise<response<V>> {
|
||||
request.originURL = request.url;
|
||||
request.url.pathname = request.params["0"]
|
||||
? "/" + request.params["0"]
|
||||
request.url.pathname = request.params['0']
|
||||
? '/' + request.params['0']
|
||||
: request.originURL.pathname;
|
||||
|
||||
let mismatchCount = 0;
|
||||
|
||||
for (let route of this.routes) {
|
||||
for (const route of this.routes) {
|
||||
const isMatched = await route.exec(request.url.pathname);
|
||||
|
||||
if (!isMatched.matched) {
|
||||
|
@ -112,7 +112,7 @@ export class router<K = any, V = any> {
|
|||
|
||||
try {
|
||||
let thisResponse: response<V> | void = responseMessage;
|
||||
for (let handler of route.handlers) {
|
||||
for (const handler of route.handlers) {
|
||||
if (
|
||||
handler.method != request.method &&
|
||||
handler.method != methodENUM.ANY
|
||||
|
@ -121,7 +121,7 @@ export class router<K = any, V = any> {
|
|||
}
|
||||
thisResponse = await handler.respond(
|
||||
request,
|
||||
thisResponse ?? responseMessage
|
||||
thisResponse ?? responseMessage,
|
||||
);
|
||||
}
|
||||
if (thisResponse instanceof response) {
|
||||
|
@ -136,10 +136,12 @@ export class router<K = any, V = any> {
|
|||
}
|
||||
if (e === AllMismatchInterrupted) mismatchCount++;
|
||||
else {
|
||||
if (typeof this.errorResponder == "function") {
|
||||
if (typeof this.errorResponder == 'function') {
|
||||
responseMessage =
|
||||
(await this.errorResponder(500, e.toString() + "\n")(request)) ??
|
||||
new response(e.toString(), 500);
|
||||
(await this.errorResponder(
|
||||
500,
|
||||
e.toString() + '\n',
|
||||
)(request)) ?? new response(e.toString(), 500);
|
||||
console.log(e);
|
||||
} else {
|
||||
throw e;
|
||||
|
@ -160,7 +162,10 @@ export class router<K = any, V = any> {
|
|||
toHandler(): handler<K, V> {
|
||||
return new handler(methodENUM.ANY, [
|
||||
(request: request<K>, responseMessage?: response<V>) => {
|
||||
return this.respond(request, responseMessage ?? new response(""));
|
||||
return this.respond(
|
||||
request,
|
||||
responseMessage ?? new response(''),
|
||||
);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
@ -168,8 +173,8 @@ export class router<K = any, V = any> {
|
|||
useErrorResponder(
|
||||
errorResponder: (
|
||||
errorCode: number,
|
||||
errorMessage?: string
|
||||
) => responder<K, V>
|
||||
errorMessage?: string,
|
||||
) => responder<K, V>,
|
||||
): this {
|
||||
this.errorResponder = errorResponder;
|
||||
return this;
|
||||
|
@ -183,10 +188,10 @@ export class rootRouter<K = any, V = any> extends router<K, V> {
|
|||
errorResponder =
|
||||
(errorCode: number, errorMessage?: string) =>
|
||||
async (_request: request<K>): Promise<response<V>> =>
|
||||
new response(errorMessage ?? "", errorCode);
|
||||
new response(errorMessage ?? '', errorCode);
|
||||
|
||||
respond = async (request: request<K>): Promise<response<V>> => {
|
||||
let responseMessage: response<V> = new response("");
|
||||
let responseMessage: response<V> = new response('');
|
||||
try {
|
||||
responseMessage = await this._respond(request, responseMessage);
|
||||
} catch (e) {
|
||||
|
@ -194,32 +199,48 @@ export class rootRouter<K = any, V = any> extends router<K, V> {
|
|||
return e;
|
||||
} else if (e === AllMismatchInterrupted) {
|
||||
responseMessage =
|
||||
(await this.errorResponder(404, "404 Not Found\n")(request)) ??
|
||||
new response("404 Not Found\n", 404);
|
||||
(await this.errorResponder(
|
||||
404,
|
||||
'404 Not Found\n',
|
||||
)(request)) ?? new response('404 Not Found\n', 404);
|
||||
} else {
|
||||
responseMessage =
|
||||
(await this.errorResponder(500, e.toString() + "\n")(request)) ??
|
||||
new response(e.toString(), 500);
|
||||
(await this.errorResponder(
|
||||
500,
|
||||
e.toString() + '\n',
|
||||
)(request)) ?? new response(e.toString(), 500);
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
return responseMessage;
|
||||
};
|
||||
|
||||
useAdapater(adapater: platformAdapaterConstructor): this {
|
||||
this.adapater = createPlatformAdapater(adapater, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
useMappingAdapter(
|
||||
mapping: { [platform: string]: platformAdapaterConstructor } = platformAdapaterMapping
|
||||
mapping: {
|
||||
[platform: string]: platformAdapaterConstructor;
|
||||
} = platformAdapaterMapping,
|
||||
): this {
|
||||
if (typeof platform == "undefined") throw new Error("Cannot detect platform");
|
||||
if (mapping[platform] == undefined) throw new Error("Platform not found in mapping");
|
||||
if (typeof platform == 'undefined')
|
||||
throw new Error('Cannot detect platform');
|
||||
if (mapping[platform] == undefined)
|
||||
throw new Error('Platform not found in mapping');
|
||||
else this.useAdapater(mapping[platform]);
|
||||
return this;
|
||||
}
|
||||
|
||||
listen(port: number): void {
|
||||
if (this.adapater == null) throw new Error("No platform adapter set");
|
||||
if (this.adapater == null) throw new Error('No platform adapter set');
|
||||
this.adapater.listen(port);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this.adapater == null) throw new Error('No platform adapter set');
|
||||
this.adapater.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
import { spawn } from "child_process";
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import Axios from "axios";
|
||||
import Axios from 'axios';
|
||||
|
||||
const Instance = Axios.create({
|
||||
baseURL: "http://localhost:3000"
|
||||
})
|
||||
baseURL: 'http://localhost:3000',
|
||||
});
|
||||
|
||||
spawn(`deno`, [
|
||||
"run", "--allow-net", `${__dirname}/../dist/test.deno.js`
|
||||
]);
|
||||
spawn(`deno`, ['run', '--allow-net', `${__dirname}/../dist/test.deno.js`]);
|
||||
|
||||
const randomString = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
const randomString = () =>
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
|
||||
describe("Test server", () => {
|
||||
test("normal 200 response", async () => {
|
||||
describe('Test server', () => {
|
||||
test('normal 200 response', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
|
||||
const { data, status } = await Instance.get("/");
|
||||
const { data, status } = await Instance.get('/');
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual("200 OK");
|
||||
})
|
||||
expect(data).toEqual('200 OK');
|
||||
});
|
||||
|
||||
test("post response", async () => {
|
||||
test('post response', async () => {
|
||||
expect.assertions(2);
|
||||
const string = randomString();
|
||||
|
||||
const { data, status } = await Instance.post("/post", string);
|
||||
const { data, status } = await Instance.post('/post', string);
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual(string);
|
||||
})
|
||||
});
|
||||
|
||||
test("change header and status code", async () => {
|
||||
test('change header and status code', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const { data, status, headers } = await Instance.get("/header");
|
||||
const { data, status, headers } = await Instance.get('/header');
|
||||
|
||||
expect(status).toEqual(204);
|
||||
expect(headers["itis"]).toEqual("work");
|
||||
expect(data).toEqual("");
|
||||
})
|
||||
expect(headers['itis']).toEqual('work');
|
||||
expect(data).toEqual('');
|
||||
});
|
||||
|
||||
test("get param", async () => {
|
||||
test('get param', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const string = randomString();
|
||||
|
@ -51,14 +51,14 @@ describe("Test server", () => {
|
|||
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual(string);
|
||||
})
|
||||
});
|
||||
|
||||
test("chain interrupted", async () => {
|
||||
test('chain interrupted', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const { data, status } = await Instance.get(`/info/foo`);
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual("hit");
|
||||
})
|
||||
})
|
||||
expect(data).toEqual('hit');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,45 +1,47 @@
|
|||
import _ from "./test-server";
|
||||
import _ from './test-server';
|
||||
|
||||
import Axios from "axios";
|
||||
import Axios from 'axios';
|
||||
|
||||
_.listen(3000);
|
||||
|
||||
const Instance = Axios.create({
|
||||
baseURL: "http://localhost:3000"
|
||||
})
|
||||
baseURL: 'http://localhost:3000',
|
||||
});
|
||||
|
||||
const randomString = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
const randomString = () =>
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
|
||||
describe("Test server", () => {
|
||||
test("normal 200 response", async () => {
|
||||
describe('Test server', () => {
|
||||
test('normal 200 response', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const { data, status } = await Instance.get("/");
|
||||
const { data, status } = await Instance.get('/');
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual("200 OK");
|
||||
})
|
||||
expect(data).toEqual('200 OK');
|
||||
});
|
||||
|
||||
test("post response", async () => {
|
||||
test('post response', async () => {
|
||||
expect.assertions(2);
|
||||
const string = randomString();
|
||||
|
||||
const { data, status } = await Instance.post("/post", string);
|
||||
const { data, status } = await Instance.post('/post', string);
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual(string);
|
||||
})
|
||||
});
|
||||
|
||||
test("change header and status code", async () => {
|
||||
test('change header and status code', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const { data, status, headers } = await Instance.get("/header");
|
||||
const { data, status, headers } = await Instance.get('/header');
|
||||
|
||||
expect(status).toEqual(204);
|
||||
expect(headers["itis"]).toEqual("work");
|
||||
expect(data).toEqual("");
|
||||
})
|
||||
expect(headers['itis']).toEqual('work');
|
||||
expect(data).toEqual('');
|
||||
});
|
||||
|
||||
test("get param", async () => {
|
||||
test('get param', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const string = randomString();
|
||||
|
@ -47,14 +49,40 @@ describe("Test server", () => {
|
|||
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual(string);
|
||||
})
|
||||
});
|
||||
|
||||
test("chain interrupted", async () => {
|
||||
test('chain interrupted', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const { data, status } = await Instance.get(`/info/foo`);
|
||||
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual("hit");
|
||||
})
|
||||
})
|
||||
expect(data).toEqual('hit');
|
||||
});
|
||||
|
||||
test('dynamic handler', async () => {
|
||||
expect.assertions(6);
|
||||
|
||||
const { status: firstStatus, data: firstData } = await Instance.get(
|
||||
`/handler`,
|
||||
);
|
||||
|
||||
const { data, status } = await Instance.get(`/handler/add`);
|
||||
|
||||
const { status: secondStatus, data: secondData } = await Instance.get(
|
||||
`/handler`,
|
||||
);
|
||||
|
||||
expect(firstStatus).toEqual(200);
|
||||
expect(firstData).toEqual('miss');
|
||||
expect(status).toEqual(200);
|
||||
expect(data).toEqual('added');
|
||||
expect(secondStatus).toEqual(200);
|
||||
expect(secondData).toEqual('hit');
|
||||
});
|
||||
});
|
||||
|
||||
afterAll((done) => {
|
||||
_.close();
|
||||
done();
|
||||
});
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import App from "./test-server";
|
||||
import App from './test-server';
|
||||
|
||||
App.listen(3000);
|
|
@ -2,23 +2,62 @@ import * as handlersJS from '../index';
|
|||
|
||||
const App = new handlersJS.rootRouter();
|
||||
|
||||
App.binding("/", App.create("GET", async () => "200 OK"));
|
||||
App.binding(
|
||||
'/',
|
||||
App.create('GET', async () => '200 OK'),
|
||||
);
|
||||
|
||||
App.binding("/post", App.create("POST", async (request: handlersJS.request<any>) => request.body));
|
||||
const dynamicHandler = new handlersJS.handler('GET', [
|
||||
async () => new handlersJS.response('miss'),
|
||||
]);
|
||||
|
||||
App.binding("/header", App.create("GET", async () => {
|
||||
const response = new handlersJS.response<any>("");
|
||||
App.binding('/handler', dynamicHandler);
|
||||
|
||||
App.binding(
|
||||
'/handler/add',
|
||||
App.create('GET', async () => {
|
||||
dynamicHandler.add(async () => new handlersJS.response('hit'));
|
||||
return 'added';
|
||||
}),
|
||||
);
|
||||
|
||||
App.binding(
|
||||
'/post',
|
||||
App.create(
|
||||
'POST',
|
||||
async (request: handlersJS.request<any>) => request.body,
|
||||
),
|
||||
);
|
||||
|
||||
App.binding(
|
||||
'/header',
|
||||
App.create('GET', async () => {
|
||||
const response = new handlersJS.response<any>('');
|
||||
response.status = 204;
|
||||
response.headers.set("itis", "work");
|
||||
response.headers.set('itis', 'work');
|
||||
return response;
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
App
|
||||
.route("/info/(.*)")
|
||||
.binding("/foo", App.create("GET", (): Promise<handlersJS.response<any>> => new Promise(resolve => {
|
||||
throw new handlersJS.response("hit")
|
||||
})))
|
||||
.binding("/(.*)", App.create("GET", async (request: handlersJS.request<any>) => request.params[0] ?? "not found"));
|
||||
App.route('/info/(.*)')
|
||||
.binding(
|
||||
'/foo',
|
||||
App.create(
|
||||
'GET',
|
||||
(): Promise<handlersJS.response<any>> =>
|
||||
new Promise(() => {
|
||||
throw new handlersJS.response('hit');
|
||||
}),
|
||||
),
|
||||
)
|
||||
.binding(
|
||||
'/(.*)',
|
||||
App.create(
|
||||
'GET',
|
||||
async (request: handlersJS.request<any>) =>
|
||||
request.params[0] ?? 'not found',
|
||||
),
|
||||
);
|
||||
|
||||
App.useMappingAdapter();
|
||||
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext"],
|
||||
"types": ["@cloudflare/workers-types", "@types/node", "@types/jest"],
|
||||
"types": [
|
||||
"@cloudflare/workers-types",
|
||||
"@types/node",
|
||||
"@types/jest",
|
||||
"bun-types"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
|
@ -23,12 +28,18 @@
|
|||
"experimentalDecorators": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"plugins": [{
|
||||
"plugins": [
|
||||
{
|
||||
"transform": "@zerollup/ts-transform-paths"
|
||||
}],
|
||||
}
|
||||
],
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["index.ts", "src/**/*.ts", "demo/**/*.ts", "types/*.d.ts"],
|
||||
"tsc-alias": {
|
||||
"resolveFullPaths": true,
|
||||
"verbose": true
|
||||
}
|
||||
// "esm": true
|
||||
}
|
|
@ -9,7 +9,7 @@ declare namespace Deno {
|
|||
};
|
||||
|
||||
export interface NetAddr {
|
||||
transport: "tcp" | "udp";
|
||||
transport: 'tcp' | 'udp';
|
||||
hostname: string;
|
||||
port: number;
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ declare namespace Deno {
|
|||
}
|
||||
|
||||
export function listen(
|
||||
options: ListenOptions & { transport?: "tcp" }
|
||||
options: ListenOptions & { transport?: 'tcp' },
|
||||
): Listener;
|
||||
|
||||
export function serveHttp(conn: Conn): HttpConn;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
type Transport = "tcp" | "udp" | "pipe";
|
||||
type Transport = 'tcp' | 'udp' | 'pipe';
|
||||
|
||||
interface ListenOptions {
|
||||
backlog?: number
|
||||
backlog?: number;
|
||||
// Disables dual stack mode.
|
||||
ipv6Only?: boolean;
|
||||
// Used on UDP only. Enable address reusing (when binding). What that means is that multiple threads or processes can bind to the same address without error (provided they all set the flag) but only the last one to bind will receive any traffic, in effect "stealing" the port from the previous listener.
|
||||
|
@ -9,7 +9,13 @@ interface ListenOptions {
|
|||
}
|
||||
|
||||
declare namespace tjs {
|
||||
const versions: { curl: string; quickjs: string; tjs: string; uv: string; wasm3: string };
|
||||
const versions: {
|
||||
curl: string;
|
||||
quickjs: string;
|
||||
tjs: string;
|
||||
uv: string;
|
||||
wasm3: string;
|
||||
};
|
||||
|
||||
export interface Address {
|
||||
readonly family: number;
|
||||
|
@ -24,15 +30,15 @@ declare namespace tjs {
|
|||
readonly readable: ReadableStream<Uint8Array>;
|
||||
|
||||
readonly remoteAddress: Address;
|
||||
readonly writeable: WritableStream<Uint8Array>
|
||||
readonly writeable: WritableStream<Uint8Array>;
|
||||
|
||||
close(): void
|
||||
read(buf: Uint8Array): Promise<number>
|
||||
write(buf: Uint8Array): Promise<number>
|
||||
close(): void;
|
||||
read(buf: Uint8Array): Promise<number>;
|
||||
write(buf: Uint8Array): Promise<number>;
|
||||
|
||||
setKeepAlive(enable?: boolean): void
|
||||
setNoDelay(enable?: boolean): void
|
||||
shutdown(): void
|
||||
setKeepAlive(enable?: boolean): void;
|
||||
setNoDelay(enable?: boolean): void;
|
||||
shutdown(): void;
|
||||
}
|
||||
|
||||
export interface Listener extends AsyncIterable<Connection> {
|
||||
|
@ -48,6 +54,6 @@ declare namespace tjs {
|
|||
transport: Transport,
|
||||
host: string,
|
||||
port?: string | number,
|
||||
options?: ListenOptions
|
||||
): Promise<Listener>
|
||||
options?: ListenOptions,
|
||||
): Promise<Listener>;
|
||||
}
|
|
@ -81,12 +81,20 @@ export default () => {
|
|||
'Promise': 'bluebird'
|
||||
})
|
||||
)
|
||||
break;
|
||||
case "bun":
|
||||
config.mode = "production";
|
||||
config.target = "node12";
|
||||
config.output.filename = "main.bun.js";
|
||||
break;
|
||||
case "deno:test":
|
||||
config.target = "webworker";
|
||||
config.output.filename = "test.deno.js";
|
||||
config.entry = "./test/test-server.deno.ts";
|
||||
break;
|
||||
default:
|
||||
config.target = "es6";
|
||||
break;
|
||||
}
|
||||
|
||||
return config;
|
||||
|
|
Loading…
Reference in New Issue