+22
| import { $ } from 'bun'; | ||
| await $`rm -rf ./dist`; | ||
| await Promise.all([ | ||
| Bun.build({ | ||
| entrypoints: ['./src/h11.ts'], | ||
| outdir: './dist', | ||
| }), | ||
| $`tsc --project tsconfig.types.json`, | ||
| Bun.build({ | ||
| entrypoints: ['./src/providers/bun.ts'], | ||
| outdir: './dist', | ||
| }), | ||
| $`tsc --project tsconfig.types.bun.json`, | ||
| Bun.build({ | ||
| entrypoints: ['./src/providers/node.ts'], | ||
| outdir: './dist', | ||
| external: ['*'], | ||
| }), | ||
| $`tsc --project tsconfig.types.node.json`, | ||
| ]); |
| import { H11 } from '../src/h11.ts'; | ||
| import { bunProviderFactory } from '../src/providers/bun.ts'; | ||
| const h11 = new H11(); | ||
| h11.get('/hello/world/sasha', () => { | ||
| return new Response(`it is a super path`); | ||
| }); | ||
| h11.get('/hello/world/:name', ({ params }) => { | ||
| return new Response(`hello world mister ${params.name}`); | ||
| }); | ||
| h11.get('/hello/**', ({ params }) => { | ||
| return new Response(`it is a wild path ${params.wild}`); | ||
| }); | ||
| const bunProvider = bunProviderFactory({ h11 }); | ||
| // const server = Bun.serve({ | ||
| // // hostname: 'hello.world', | ||
| // unix: '\0benchmark-h11-bun', | ||
| // // port: '443', | ||
| // async fetch(req, server) { | ||
| // console.log('-----', 'req.url', req.url); | ||
| // // console.log('-----', 'args', args); | ||
| // // req.body. | ||
| // const res = await bunProvider(req, server); | ||
| // return res; | ||
| // }, | ||
| // }); | ||
| // console.log('-----', 'server', server.url); | ||
| Bun.serve({ | ||
| unix: '/tmp/my-socket.sock', // abstract namespace socket | ||
| fetch(req) { | ||
| return new Response(`404!`); | ||
| }, | ||
| }); |
| import { createServer } from 'node:http'; | ||
| import { H11 } from '../src/h11.ts'; | ||
| import { | ||
| getAddress, | ||
| nodeProviderFactory, | ||
| } from '../src/providers/nodejs-old.ts'; | ||
| const h11 = new H11(); | ||
| h11.get('/hello/world/sasha', () => { | ||
| return new Response(`it is a super path`); | ||
| }); | ||
| h11.get('/hello/world/:name', ({ params }) => { | ||
| return new Response(`hello world mister ${params.name}`); | ||
| }); | ||
| h11.get('/hello/**', ({ params }) => { | ||
| return new Response(`it is a wild path ${params.wild}`); | ||
| }); | ||
| h11.get('/**', ({ req }) => { | ||
| return new Response(req.url); | ||
| }); | ||
| const nodeProvider = nodeProviderFactory({ h11 }); | ||
| const server = createServer((req, res) => { | ||
| nodeProvider({ | ||
| req, | ||
| res, | ||
| origin: getAddress(server) || 'http://localhost:3000', | ||
| }); | ||
| }); | ||
| server.listen(3000); |
| import { Readable } from 'stream'; | ||
| console.log('-----', 'Readable', Readable); |
+102
| import { Radix } from './radix/radix.ts'; | ||
| const FORCE_STOP = Symbol('FORCE_STOP'); | ||
| export type Method = 'GET' | 'POST' | 'DELET' | 'PUT' | 'PATCH'; | ||
| type HandlerResponse = Promise<Response> | Response; | ||
| export type ExecProps = { | ||
| req: Request; | ||
| providers: Record<string, any>; | ||
| data: Record<any, any>; | ||
| }; | ||
| export type HandlerPure = (props: Props) => HandlerResponse; | ||
| export type HandlerContainer = { | ||
| handler: HandlerPure; | ||
| plugins: Plugin[]; | ||
| }; | ||
| type Props = { | ||
| req: Request; | ||
| params: Record<string, string>; | ||
| data: Record<any, any>; | ||
| }; | ||
| type Handler = HandlerPure | HandlerContainer; | ||
| export type Plugin = (props: Props) => HandlerResponse; | ||
| type NotFoundHandler = (req: Request) => HandlerResponse; | ||
| type ErrorHandler = (props: { error: Error } & Props) => HandlerResponse; | ||
| const defaultOnNotFound: NotFoundHandler = (req) => { | ||
| console.log(`h9: Not found ${req.url}`); | ||
| return new Response('Not Found', { | ||
| status: 404, | ||
| statusText: 'Not Found 404', | ||
| headers: { | ||
| 'Content-Type': 'text/plain', | ||
| }, | ||
| }); | ||
| }; | ||
| const defaultOnError: ErrorHandler = ({ req, error }) => { | ||
| console.error(`h9: Error ${req.url} - ${error.message}`); | ||
| return new Response('Error 500', { | ||
| status: 500, | ||
| statusText: 'System error 500', | ||
| headers: { | ||
| 'Content-Type': 'text/plain', | ||
| }, | ||
| }); | ||
| }; | ||
| export class H11<TExecProps extends ExecProps = ExecProps> { | ||
| types!: TExecProps; | ||
| radix = new Radix(); | ||
| onNotFound: NotFoundHandler = defaultOnNotFound; | ||
| onError: ErrorHandler = defaultOnError; | ||
| private addRoute(pattern: string, method: Method, handler: Handler) { | ||
| const preparedHandler = | ||
| typeof handler === 'function' ? { handler, plugins: [] } : handler; | ||
| this.radix.add(pattern, method, preparedHandler); | ||
| } | ||
| get(pattern: string, handler: Handler) { | ||
| this.addRoute(pattern, 'GET', handler); | ||
| return this; | ||
| } | ||
| post(pattern: string, handler: Handler) { | ||
| this.addRoute(pattern, 'POST', handler); | ||
| return this; | ||
| } | ||
| async exec({ req, data, providers }: TExecProps) { | ||
| const url = new URL(req.url); | ||
| const findResult = this.radix.find(url.pathname, req.method as any); | ||
| if (!findResult) { | ||
| return this.onNotFound(req); | ||
| } | ||
| const props = { req, params: findResult.params, data, providers }; | ||
| try { | ||
| for (const plugin of findResult.handler.plugins) { | ||
| const response = await plugin(props); | ||
| if (response) { | ||
| return response; | ||
| } | ||
| } | ||
| const response = await findResult.handler.handler(props); | ||
| return response; | ||
| } catch (error) { | ||
| return this.onError({ ...props, error: error as Error }); | ||
| } | ||
| } | ||
| } |
| import type { Server } from 'bun'; | ||
| import type { H11 } from '../h11.ts'; | ||
| export const bunProviderFactory = ({ h11 }: { h11: H11 }) => { | ||
| return async (req: Request, server: Server) => { | ||
| const res = await h11.exec({ | ||
| req, | ||
| providers: { | ||
| bun: { | ||
| req, | ||
| server, | ||
| }, | ||
| }, | ||
| data: {}, | ||
| }); | ||
| return res; | ||
| }; | ||
| }; |
| import type { IncomingMessage, ServerResponse, Server } from 'node:http'; | ||
| import { Readable } from 'node:stream'; | ||
| import type { H11 } from '../h11.ts'; | ||
| const joinPaths = (elem1: string, elem2: string) => { | ||
| if (elem1.at(-1) === '/') { | ||
| if (elem2[0] === '/') { | ||
| return `${elem1}${elem2.slice(1)}`; | ||
| } else { | ||
| return elem1 + elem2; | ||
| } | ||
| } else { | ||
| if (elem2[0] === '/') { | ||
| return elem1 + elem2; | ||
| } else { | ||
| return `${elem1}/${elem2}`; | ||
| } | ||
| } | ||
| }; | ||
| export const getAddress = (server: Server) => { | ||
| const address = server.address(); | ||
| if (typeof address !== 'object' || address === null) { | ||
| return; | ||
| } | ||
| const protocol = 'http'; | ||
| const port = `:${address.port}`; | ||
| let url: string; | ||
| if (address.family === 'IPv6') { | ||
| url = `[${address.address}]`; | ||
| } else { | ||
| url = address.address; | ||
| } | ||
| return `${protocol}://${url}${port}`; | ||
| }; | ||
| const createReqFromNode = (req: IncomingMessage, origin: string) => { | ||
| const method = req.method; | ||
| const headers = new Headers(req.headers as Record<string, string>); | ||
| const body = | ||
| method === 'GET' || method === 'HEAD' | ||
| ? null | ||
| : (Readable.toWeb(req) as any as ReadableStream); | ||
| // Создаем и возвращаем новый Request объект | ||
| return new Request(joinPaths(origin, req.url!), { | ||
| method, | ||
| headers, | ||
| body, | ||
| }); | ||
| }; | ||
| const sendNodeRes = ( | ||
| responseNative: Response, | ||
| res: ServerResponse<IncomingMessage> | ||
| ) => { | ||
| const headers: Record<string, string> = {}; | ||
| responseNative.headers.forEach((value, key) => (headers[key] = value)); | ||
| res.writeHead(responseNative.status, responseNative.statusText, headers); | ||
| if (responseNative.body) { | ||
| const resStream = Readable.fromWeb(responseNative.body as any); | ||
| resStream.pipe(res); | ||
| } else { | ||
| res.end(); | ||
| } | ||
| }; | ||
| export const nodeProviderFactory = | ||
| ({ h11 }: { h11: H11 }) => | ||
| async ({ | ||
| req, | ||
| res, | ||
| origin, | ||
| }: { | ||
| req: IncomingMessage; | ||
| res: ServerResponse<IncomingMessage>; | ||
| origin: string; | ||
| }) => { | ||
| const preapredOrigin = req.headers['host'] || origin; | ||
| const preparedRes = createReqFromNode(req, preapredOrigin); | ||
| const execRes = await h11.exec({ | ||
| req: preparedRes, | ||
| data: {}, | ||
| providers: { | ||
| node: { | ||
| req, | ||
| res, | ||
| }, | ||
| }, | ||
| }); | ||
| sendNodeRes(execRes, res); | ||
| }; |
| export type ProviderResponse = { | ||
| req: Request; | ||
| providers: Record<string, any>; | ||
| }; | ||
| type A = Record<string, any>; | ||
| type B = { hello: 'world' }; | ||
| type C = A & B; | ||
| const c: C = null as any as C; |
| { | ||
| "extends": "./tsconfig.types.json", | ||
| "include": ["src/providers/bun.ts"] | ||
| } |
| { | ||
| "extends": "./tsconfig.types.json", | ||
| "include": ["src/providers/node.ts"] | ||
| } |
+17
-4
| { | ||
| "name": "h11", | ||
| "module": "src/h9.ts", | ||
| "type": "module", | ||
| "version": "0.0.1", | ||
| "version": "0.0.2", | ||
| "scripts": { | ||
| "build": "bun build ./src/h9.ts --outdir ./dist && bun run types", | ||
| "types": "tsc --project tsconfig.types.json" | ||
| "build": "bun run ./build.ts" | ||
| }, | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/types/h11.d.ts", | ||
| "default": "./dist/h11.ts" | ||
| }, | ||
| "./bun": { | ||
| "types": "./dist/types/providers/bun.d.ts", | ||
| "default": "./dist/bun.ts" | ||
| }, | ||
| "./node": { | ||
| "types": "./dist/types/providers/node.d.ts", | ||
| "default": "./dist/bun.ts" | ||
| } | ||
| }, | ||
| "devDependencies": { | ||
@@ -15,4 +27,5 @@ "@types/bun": "latest", | ||
| "dependencies": { | ||
| "mitata": "^0.1.14", | ||
| "utftu": "^1.0.26" | ||
| } | ||
| } |
@@ -1,2 +0,2 @@ | ||
| import type { HandlerContainer, Method } from '../../h9.ts'; | ||
| import type { HandlerContainer, Method } from '../../h11.ts'; | ||
@@ -3,0 +3,0 @@ export class Node { |
@@ -1,2 +0,2 @@ | ||
| import type { HandlerContainer, Method } from '../h9.ts'; | ||
| import type { HandlerContainer, Method } from '../h11.ts'; | ||
| import { Node, addNodeToChildren } from './node/node.ts'; | ||
@@ -6,2 +6,3 @@ | ||
| // /hello/world/:name/:family | ||
| export class Radix { | ||
@@ -85,2 +86,3 @@ root = new Node({ segment: '' }); | ||
| // hello/world/:name | ||
| add(pattern: string, method: Method = 'GET', handler: HandlerContainer) { | ||
@@ -87,0 +89,0 @@ const patternSegments = pattern.slice(1).split('/'); |
@@ -9,3 +9,3 @@ { | ||
| }, | ||
| "include": ["src/h9.ts"] | ||
| "include": ["src/h11.ts"] | ||
| } |
-98
| import { waitTime } from 'utftu'; | ||
| // import { H4 } from './h4.ts'; | ||
| // const h4 = new H4(); | ||
| // h4.get('/hello/world/sasha', () => { | ||
| // return new Response(`it is a super path`); | ||
| // }); | ||
| // h4.get('/hello/world/:name', ({ params }) => { | ||
| // return new Response(`hello world mister ${params.name}`); | ||
| // }); | ||
| // h4.get('/hello/**', ({ params }) => { | ||
| // return new Response(`it is a wild path ${params.wild}`); | ||
| // }); | ||
| // // hello/word/sasha | ||
| // h4.get('/test', () => { | ||
| // const stream = new ReadableStream({ | ||
| // async start(controller) { | ||
| // // Генерация данных | ||
| // const encoder = new TextEncoder(); | ||
| // const data = ['<body>', 'World', 'Streamed', 'Response']; | ||
| // for (const chunk of data) { | ||
| // controller.enqueue(encoder.encode(chunk + '\n')); | ||
| // await new Promise((resolve) => { | ||
| // setTimeout(resolve, 1000); | ||
| // }); | ||
| // } | ||
| // controller.close(); | ||
| // }, | ||
| // }); | ||
| // const response = new Response(stream, { | ||
| // headers: { | ||
| // 'Content-Type': 'text/plain', | ||
| // }, | ||
| // }); | ||
| // return response; | ||
| // }); | ||
| const firstPart = ` | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| </head> | ||
| <body> | ||
| ${'<div>1111</div>'.repeat(20)} | ||
| `; | ||
| const firstPart1 = ` | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| </head> | ||
| <body> | ||
| ${'<div>1111</div>'.repeat(100)} | ||
| `; | ||
| const secondPart = ` | ||
| <div>${'222'.repeat(1000)}</div> | ||
| </body> | ||
| </html> | ||
| `; | ||
| const headers = { | ||
| // Vary: 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Accept-Encoding', | ||
| // link: '</_next/static/media/a34f9d1faa5f3315-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2"', | ||
| // 'Cache-Control': 'no-store, must-revalidate', | ||
| // 'X-Powered-By': 'Next.js', | ||
| 'Content-Type': 'text/html; charset=utf-8', | ||
| // 'Content-Encoding': 'gzip', | ||
| // Date: 'Thu, 29 Aug 2024 20:52:08 GMT', | ||
| // Connection: 'keep-alive', | ||
| // 'Keep-Alive': 'timeout=5', | ||
| // 'Transfer-Encoding': 'chunked', | ||
| }; | ||
| console.log('-----', 'header', headers); | ||
| Bun.serve({ | ||
| fetch(req) { | ||
| const stream = new ReadableStream({ | ||
| async start(controller) { | ||
| controller.enqueue(firstPart); | ||
| await waitTime(4000); | ||
| controller.enqueue(secondPart); | ||
| controller.close(); | ||
| }, | ||
| }); | ||
| return new Response(stream, { | ||
| headers: headers, | ||
| }); | ||
| }, | ||
| }); |
-95
| import { Radix } from './radix/radix.ts'; | ||
| export type Method = 'GET' | 'POST' | 'DELET' | 'PUT' | 'PATCH'; | ||
| type HandlerResponse = Promise<Response> | Response; | ||
| type PluginReponse = HandlerResponse | Promise<void> | void; | ||
| export type HandlerPure = (props: Props) => HandlerResponse; | ||
| export type HandlerContainer = { | ||
| handler: HandlerPure; | ||
| plugins: Plugin[]; | ||
| }; | ||
| type Props = { | ||
| req: Request; | ||
| params: Record<string, string>; | ||
| data: Record<any, any>; | ||
| }; | ||
| type Handler = HandlerPure | HandlerContainer; | ||
| export type Plugin = (props: Props) => HandlerResponse; | ||
| type NotFoundHandler = (req: Request) => HandlerResponse; | ||
| type ErrorHandler = (props: { error: Error } & Props) => HandlerResponse; | ||
| const defaultOnNotFound: NotFoundHandler = (req) => { | ||
| console.log(`h9: Not found ${req.url}`); | ||
| return new Response('Not Found', { | ||
| status: 404, | ||
| statusText: 'Not Found 404', | ||
| headers: { | ||
| 'Content-Type': 'text/plain', | ||
| }, | ||
| }); | ||
| }; | ||
| const defaultOnError: ErrorHandler = ({ req, error }) => { | ||
| console.error(`h9: Error ${req.url} - ${error.message}`); | ||
| return new Response('Error 500', { | ||
| status: 500, | ||
| statusText: 'System error 500', | ||
| headers: { | ||
| 'Content-Type': 'text/plain', | ||
| }, | ||
| }); | ||
| }; | ||
| export class H9 { | ||
| radix = new Radix(); | ||
| onNotFound: NotFoundHandler = defaultOnNotFound; | ||
| onError: ErrorHandler = defaultOnError; | ||
| private addRoute(pattern: string, method: Method, handler: Handler) { | ||
| const preparedHandler = | ||
| typeof handler === 'function' ? { handler, plugins: [] } : handler; | ||
| this.radix.add(pattern, method, preparedHandler); | ||
| } | ||
| get(pattern: string, handler: Handler) { | ||
| this.addRoute(pattern, 'GET', handler); | ||
| return this; | ||
| } | ||
| post(pattern: string, handler: Handler) { | ||
| this.addRoute(pattern, 'POST', handler); | ||
| return this; | ||
| } | ||
| async exec(req: Request) { | ||
| const url = new URL(req.url); | ||
| const findResult = this.radix.find(url.pathname, req.method as any); | ||
| if (!findResult) { | ||
| return this.onNotFound(req); | ||
| } | ||
| const data = {}; | ||
| const props = { req, params: findResult.params, data }; | ||
| try { | ||
| for (const plugin of findResult.handler.plugins) { | ||
| const response = await plugin(props); | ||
| if (response) { | ||
| return response; | ||
| } | ||
| } | ||
| const response = await findResult.handler.handler(props); | ||
| return response; | ||
| } catch (error) { | ||
| return this.onError({ ...props, error: error as Error }); | ||
| } | ||
| } | ||
| } |
| // import http from 'node:http'; | ||
| // const a = http.createServer(); | ||
| // const req = new Request(); | ||
| // a.listen(); |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Network access
Supply chain riskThis module accesses the network.
20606
22.29%19
46.15%524
29.7%2
100%3
200%+ Added
+ Added