add support for http

This commit is contained in:
zizifn
2022-12-06 00:00:20 +08:00
committed by zizifn
parent be193a1116
commit cc05814c24
25 changed files with 279 additions and 195 deletions

2
.gitignore vendored
View File

@@ -40,3 +40,5 @@ Thumbs.db
## ##
**/config-local.json **/config-local.json
**/.env

View File

@@ -5,6 +5,8 @@
**v2ray-heroku 由于 heroku 取消免费,项目已经死了。 这是新的项目。** **v2ray-heroku 由于 heroku 取消免费,项目已经死了。 这是新的项目。**
> 项目正在开发,基本可用,会有 bug。。 > 项目正在开发,基本可用,会有 bug。。
> **请定期按照 github 的提示,同步 code 到自己的项目**。
> ![sync](./doc/sync.jpg)
> 本项目纯属技术性验证,探索最新的 web standard。不给予任何保证。请大家酌情使用。如果有兴趣想一起进行技术探讨可以联系我。💕 > 本项目纯属技术性验证,探索最新的 web standard。不给予任何保证。请大家酌情使用。如果有兴趣想一起进行技术探讨可以联系我。💕

View File

@@ -1,5 +1,4 @@
import { serve } from 'https://deno.land/std@0.167.0/http/server.ts'; import { serve } from 'https://deno.land/std@0.167.0/http/server.ts';
const userID = Deno.env.get('UUID'); const userID = Deno.env.get('UUID');
const handler = async (request: Request): Promise<Response> => { const handler = async (request: Request): Promise<Response> => {
@@ -35,47 +34,9 @@ ${userID ? 'has UUID env' : 'no UUID env'}
hostname: serverAddress, hostname: serverAddress,
}); });
// connection.write( // request.body readablestream end casue socket to be end, this will casue socket send FIN package early
// new TextEncoder().encode('GET http://www.baidu.com/ HTTP/1.1\r\n') // and casue deno can't get TCP pcakge.
// );
// connection.write(new TextEncoder().encode('Host: www.baidu.com\r\n\r\n'));
// connection.close();
// GET / HTTP/1.1
// Host: www.baidu.com
// User-Agent: curl/7.83.1
// Accept: */*
// const body2 = new ReadableStream({
// start(controller) {
// controller.enqueue(new TextEncoder().encode('GET / HTTP/1.1\r\n'));
// controller.enqueue(new TextEncoder().encode('Host: www.baidu.com\r\n'));
// controller.enqueue(
// new TextEncoder().encode('User-Agent: curl/7.83.1\r\n')
// );
// controller.enqueue(new TextEncoder().encode('Accept: */*\r\n\r\n'));
// // controller.close();
// },
// cancel() {},
// });
// for await (const chunk of body2) {
// connection.write(chunk);
// }
const proxyResp = request.body?.pipeThrough(connection); const proxyResp = request.body?.pipeThrough(connection);
// const proxyResp = request.body
// ?.pipeThrough(
// new TransformStream({
// async transform(chunk, controller) {
// console.log('transform');
// controller.enqueue(chunk);
// },
// async flush(controller) {
// console.log('flush');
// return new Promise((res) => setTimeout(res, 1000));
// },
// })
// )
// .pipeThrough(connection);
return new Response(proxyResp, { return new Response(proxyResp, {
status: 200, status: 200,
headers: {}, headers: {},

View File

@@ -1,15 +1,12 @@
import { serve } from 'https://deno.land/std@0.167.0/http/server.ts'; import { serve } from 'https://deno.land/std@0.167.0/http/server.ts';
console.log('Current Deno version', Deno.version.deno);
const handler = async (request: Request): Promise<Response> => { const handler = async (request: Request): Promise<Response> => {
const connection = await Deno.connect({ const connection = await Deno.connect({
port: 80, port: 80,
hostname: 'www.baidcu.com', hostname: 'www.baidu.com',
}); });
// GET / HTTP/1.1
// Host: www.baidu.com
// User-Agent: curl/7.83.1
// Accept: */*
const body2 = new ReadableStream({ const body2 = new ReadableStream({
start(controller) { start(controller) {
controller.enqueue(new TextEncoder().encode('GET / HTTP/1.1\r\n')); controller.enqueue(new TextEncoder().encode('GET / HTTP/1.1\r\n'));
@@ -18,23 +15,24 @@ const handler = async (request: Request): Promise<Response> => {
new TextEncoder().encode('User-Agent: curl/7.83.1\r\n') new TextEncoder().encode('User-Agent: curl/7.83.1\r\n')
); );
controller.enqueue(new TextEncoder().encode('Accept: */*\r\n\r\n')); controller.enqueue(new TextEncoder().encode('Accept: */*\r\n\r\n'));
controller.close(); // 注释这个就好用 controller.close();
}, },
cancel() {}, cancel() {},
}); });
// 或者不用 pipeThrough 直接write
// for await (const chunk of body2) { // for await (const chunk of body2) {
// connection.write(chunk); // console.log('11');
// } // }
// const proxyResp = connection.readable;
// -----------
const proxyResp = body2?.pipeThrough(connection); const proxyResp = body2?.pipeThrough(connection);
return new Response(proxyResp, { for await (const chunk of proxyResp) {
console.log('11');
}
return new Response('111', {
status: 200, status: 200,
headers: {}, headers: {
'x-ray': 'xxxx',
},
}); });
}; };

View File

@@ -19,19 +19,6 @@ const httpProxyServer = createServer(async (req, resp) => {
`Client Connected To Proxy, client http version is ${req.httpVersion}, ${clientSocketLoggerInfo}}` `Client Connected To Proxy, client http version is ${req.httpVersion}, ${clientSocketLoggerInfo}}`
); );
const raws = rawHTTPPackage(req);
const readableStream = new Readable({
async read() {
const { value, done } = await raws.next();
if (!done) {
this.push(value);
}
},
destroy() {
this.push(null);
},
});
// make call to edge http server // make call to edge http server
// 1. forward all package remote, socket over http body // 1. forward all package remote, socket over http body
const { body, headers, statusCode, trailers } = await undici.request( const { body, headers, statusCode, trailers } = await undici.request(
@@ -44,39 +31,30 @@ const httpProxyServer = createServer(async (req, resp) => {
// "Content-Type": "text/plain", // "Content-Type": "text/plain",
}, },
method: 'POST', method: 'POST',
// body: Readable.from(rawHTTPPackage(req)), body: Readable.from(concatStreams([rawHTTPPackage(req), req.socket])),
body: readableStream,
// body: rawHTTPHeader(req),
// body: req,
} }
); );
// for await (const item of rawHTTPPackage(req)) { console.log(`${clientSocketLoggerInfo} remote server return ${statusCode}`);
// myReadable.push(item);
// }
// console.log(headers, statusCode);
// for await (let chunk of body) {
// console.log(chunk.toString());
// }
// 2. forward remote reponse body to clientSocket // 2. forward remote reponse body to clientSocket
for await (const chunk of body) {
pipeline(body, resp, (error) => { req.socket.write(chunk);
console.log( }
`${clientSocketLoggerInfo} remote server to clientSocket has error: ` +
error
);
resp.destroy();
});
body.on('error', (err) => { body.on('error', (err) => {
console.log('body error', err); console.log(`${clientSocketLoggerInfo} body error`, err);
});
body.on('data', () => {
if (!readableStream.closed) {
readableStream.push(null);
}
}); });
// issue with pipeline
// https://stackoverflow.com/questions/55959479/error-err-stream-premature-close-premature-close-in-node-pipeline-stream
// pipeline(body, req.socket, (error) => {
// console.log(
// `${clientSocketLoggerInfo} remote server to clientSocket has error: ` +
// error
// );
// req.socket.end();
// req.socket.destroy();
// });
} catch (error) { } catch (error) {
resp.destroy(); req.socket.end();
req.socket.destroy();
console.log('${clientSocketLogger} has error ', error); console.log('${clientSocketLogger} has error ', error);
} }
}); });
@@ -96,7 +74,6 @@ httpProxyServer.on('connect', async (req, clientSocket, head) => {
`HTTP/${req.httpVersion} 200 Connection Established\r\n\r\n` `HTTP/${req.httpVersion} 200 Connection Established\r\n\r\n`
); );
console.log(config);
// make call to edge http server // make call to edge http server
// 1. forward all package remote, socket over http body // 1. forward all package remote, socket over http body
const { body, headers, statusCode, trailers } = await undici.request( const { body, headers, statusCode, trailers } = await undici.request(
@@ -114,14 +91,21 @@ httpProxyServer.on('connect', async (req, clientSocket, head) => {
); );
console.log(`${clientSocketLoggerInfo} remote server return ${statusCode}`); console.log(`${clientSocketLoggerInfo} remote server return ${statusCode}`);
// 2. forward remote reponse body to clientSocket // 2. forward remote reponse body to clientSocket
pipeline(body, clientSocket, (error) => { // 2. forward remote reponse body to clientSocket
console.log( for await (const chunk of body) {
`${clientSocketLoggerInfo} remote server to clientSocket has error: `, clientSocket.write(chunk);
error }
); body.on('error', (err) => {
body?.destroy(); console.log(`${clientSocketLoggerInfo} body error`, err);
clientSocket.destroy();
}); });
// pipeline(body, clientSocket, (error) => {
// console.log(
// `${clientSocketLoggerInfo} remote server to clientSocket has error: `,
// error
// );
// body?.destroy();
// clientSocket.destroy();
// });
clientSocket.on('error', (e) => { clientSocket.on('error', (e) => {
body?.destroy(); body?.destroy();
clientSocket.destroy(); clientSocket.destroy();

View File

@@ -1,88 +0,0 @@
var net = require('net');
var http = require('http');
var url = require('url');
var proxyServer = http.createServer(httpOptions);
// handle http proxy requests
function httpOptions(clientReq, clientRes) {
var reqUrl = url.parse(clientReq.url);
console.log(reqUrl);
console.log('proxy for http request: ' + reqUrl.href);
var options = {
hostname: reqUrl.hostname,
port: reqUrl.port,
path: reqUrl.path,
method: clientReq.method,
headers: clientReq.headers,
};
// create socket connection on behalf of client, then pipe the response to client response (pass it on)
var serverConnection = http.request(options, function (res) {
clientRes.writeHead(res.statusCode, res.headers);
res.pipe(clientRes);
});
clientReq.pipe(serverConnection);
clientReq.on('error', (e) => {
console.log('client socket error: ' + e);
});
serverConnection.on('error', (e) => {
console.log('server connection error: ' + e);
});
}
// handle https proxy requests (CONNECT method)
proxyServer.on('connect', (clientReq, clientSocket, head) => {
var reqUrl = url.parse('https://' + clientReq.url);
console.log(
'proxy for https request: ' + reqUrl.href + '(path encrypted by ssl)'
);
var options = {
port: reqUrl.port,
host: reqUrl.hostname,
};
// create socket connection for client, then pipe (redirect) it to client socket
var serverSocket = net.connect(options, () => {
clientSocket.write(
'HTTP/' +
clientReq.httpVersion +
' 200 Connection Established\r\n' +
'Proxy-agent: Node.js-Proxy\r\n' +
'\r\n',
'UTF-8',
() => {
// creating pipes in both ends
serverSocket.write(head);
serverSocket.pipe(clientSocket);
clientSocket.pipe(serverSocket);
}
);
});
clientSocket.on('error', (e) => {
console.log('client socket error: ' + e);
serverSocket.end();
});
serverSocket.on('error', (e) => {
console.log('forward proxy server connection error: ' + e);
clientSocket.end();
});
});
proxyServer.on('clientError', (err, clientSocket) => {
console.log('client error: ' + err);
clientSocket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
proxyServer.listen(2560);
console.log('forward proxy server started, listening on port 2560');
module.exports = proxyServer;

View File

@@ -7,7 +7,7 @@ import { writeFileSync, existsSync, readFileSync } from 'fs';
import { exit } from 'node:process'; import { exit } from 'node:process';
import * as url from 'node:url'; import * as url from 'node:url';
import * as undici from 'undici'; import * as undici from 'undici';
import { concatStreams } from './lib/helper'; import { concatStreams } from '../lib/helper';
let config: { let config: {
port: string; port: string;

View File

@@ -7,7 +7,7 @@ import { writeFileSync, existsSync, readFileSync } from 'fs';
import { exit } from 'node:process'; import { exit } from 'node:process';
import * as url from 'node:url'; import * as url from 'node:url';
import * as undici from 'undici'; import * as undici from 'undici';
import { concatStreams } from './lib/helper';
import * as http from 'node:http'; import * as http from 'node:http';
let config: { let config: {

View File

@@ -13,5 +13,6 @@
{ {
"path": "./tsconfig.spec.json" "path": "./tsconfig.spec.json"
} }
] ],
"exclude": ["**test/*.ts"]
} }

3
libs/test/.babelrc Normal file
View File

@@ -0,0 +1,3 @@
{
"presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
}

18
libs/test/.eslintrc.json Normal file
View File

@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

11
libs/test/README.md Normal file
View File

@@ -0,0 +1,11 @@
# test
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test test` to execute the unit tests via [Jest](https://jestjs.io).
## Running lint
Run `nx lint test` to execute the lint via [ESLint](https://eslint.org/).

16
libs/test/jest.config.ts Normal file
View File

@@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'test',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
testEnvironment: 'node',
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/libs/test',
};

95
libs/test/node.mjs Normal file
View File

@@ -0,0 +1,95 @@
import { createServer } from 'node:http';
import { connect } from 'node:net';
import { Readable, Duplex, Writable } from 'node:stream';
import { ReadableStream, WritableStream } from 'node:stream/web';
const httpServer = createServer(async (req, resp) => {
// const rawHttp = new ReadableStream({
// start(controller) {
// controller.enqueue(new TextEncoder().encode('GET / HTTP/1.1\r\n'));
// controller.enqueue(new TextEncoder().encode('Host: www.baidu.com\r\n'));
// controller.enqueue(
// new TextEncoder().encode('User-Agent: curl/7.83.1\r\n')
// );
// controller.enqueue(new TextEncoder().encode('Accept: */*\r\n\r\n'));
// controller.close();
// },
// cancel() {},
// });
// async function* resposne() {
// for await (const chunk of [
// 'GET / HTTP/1.1\r\n',
// 'Host: www.baidu.com\r\n',
// 'User-Agent: curl/7.83.1\r\n',
// 'Accept: */*\r\n\r\n',
// ]) {
// yield chunk;
// }
// }
// const buffer = resposne();
// const rawHttp = new Readable({
// async read() {
// console.log('pull----');
// const { value, done } = await buffer.next();
// if (!done) {
// this.push(value);
// }
// },
// });
const rawHttp = Readable.from([
'GET / HTTP/1.1\r\n',
'Host: www.baidu.com\r\n',
'User-Agent: curl/7.83.1\r\n\r\n',
]);
// rawHttp.p;
// rawHttp.push('Accept: */*\r\n\r\n');
rawHttp.on('end', () => {
console.log('rawHttp--end-----');
});
const socket = connect(
{
port: 80,
host: 'www.baidu.com',
},
() => {
console.log('connected');
resp.writeHead(200);
rawHttp.pipe(socket).pipe(resp);
}
);
// .pipe(resp)
// .on('close', () => {
// console.log('--close-----');
// });
});
httpServer.listen('8888', () => {
console.log('Server runnig at http://localhost:8888');
});
// const httpServer = createServer(async (req, resp) => {
// const rawHttp = new ReadableStream({
// start(controller) {
// controller.enqueue(new TextEncoder().encode('GET / HTTP/1.1\r\n'));
// controller.enqueue(new TextEncoder().encode('Host: www.baidu.com\r\n'));
// controller.enqueue(
// new TextEncoder().encode('User-Agent: curl/7.83.1\r\n')
// );
// controller.enqueue(new TextEncoder().encode('Accept: */*\r\n\r\n'));
// controller.close();
// },
// cancel() {},
// });
// const socket = connect({
// port: 80,
// host: 'www.baidu.com',
// });
// resp.writeHead(200);
// const webStreamSocket = rawHttp.pipeThrough(Duplex.toWeb(socket));
// Readable.fromWeb(webStreamSocket).pipe(resp);
// });
// httpServer.listen('8888', () => {
// console.log('Server runnig at http://localhost:8888');
// });

24
libs/test/project.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "test",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/test/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/test/**/*.ts"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/test/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}

1
libs/test/src/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './lib/test';

View File

@@ -0,0 +1,7 @@
import { test } from './test';
describe('test', () => {
it('should work', () => {
expect(test()).toEqual('test');
});
});

View File

@@ -0,0 +1,3 @@
export function test(): string {
return 'test';
}

13
libs/test/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
"include": ["**/*.ts"]
}

View File

@@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.jsx",
"**/*.spec.jsx",
"**/*.d.ts"
]
}

View File

@@ -14,7 +14,9 @@
"skipLibCheck": true, "skipLibCheck": true,
"skipDefaultLibCheck": true, "skipDefaultLibCheck": true,
"baseUrl": ".", "baseUrl": ".",
"paths": {} "paths": {
"@edge-bypass/test": ["libs/test/src/index.ts"]
}
}, },
"exclude": ["node_modules", "tmp"] "exclude": ["node_modules", "tmp"]
} }