diff --git a/README.md b/README.md index 3b8bc2c..aa1ae0e 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ proxy to www.google.com:443 and remote return 200 ### 浏览器 switchyomega 设置 具体安装和配置,请查看官网. -https://proxy-switchyomega.com/settings/ +https://proxy-switchyomega.com/proxy/ 除了端口(port)和下图可能不一样。其他都应该是一样的。 diff --git a/apps/deno-bypass/.env b/apps/deno-bypass/.env new file mode 100644 index 0000000..efb690d --- /dev/null +++ b/apps/deno-bypass/.env @@ -0,0 +1 @@ +UUID=***REMOVED****** \ No newline at end of file diff --git a/apps/deno-bypass/project.json b/apps/deno-bypass/project.json index 6eaa0b6..4ee47ee 100644 --- a/apps/deno-bypass/project.json +++ b/apps/deno-bypass/project.json @@ -7,13 +7,13 @@ "run": { "executor": "nx:run-commands", "options": { - "command": "deno run --allow-net apps/deno-bypass/src/bypass.ts " + "command": "deno run --allow-net --allow-env --allow-write apps/deno-bypass/src/bypass.ts " } }, "serve": { "executor": "nx:run-commands", "options": { - "command": "deno run --allow-net --watch apps/deno-bypass/src/bypass.ts " + "command": "deno run --allow-net --allow-write --allow-env --watch apps/deno-bypass/src/bypass.ts " } } }, diff --git a/apps/deno-bypass/src/bypass copy.ts b/apps/deno-bypass/src/bypass copy.ts new file mode 100644 index 0000000..3bbc026 --- /dev/null +++ b/apps/deno-bypass/src/bypass copy.ts @@ -0,0 +1,70 @@ +import { serve } from 'https://deno.land/std@0.167.0/http/server.ts'; + +const userID = Deno.env.get('UUID') || '***REMOVED******'; + +const handler = async (request: Request): Promise => { + const headers = request.headers; + const serverAddress = headers.get('x-host') || ''; + const remotePort = headers.get('x-port') || 443; + const uuid = headers.get('x-uuid'); + + if (!serverAddress || !remotePort || !userID) { + return new Response( + `Version 0.0.1-2022/12/04!! +${userID ? 'has UUID env' : 'no UUID env'} +感谢 deno deploy 严肃对待 web standard。支持 HTTP request & response streaming。 + `, + { + status: 200, + headers: {}, + } + ); + } + console.log( + `want to proxy to server address ${serverAddress}, and port ${remotePort}` + ); + + if (uuid !== userID) { + return new Response('Do not send right UUID!', { + status: 403, + headers: {}, + }); + } + const connection = await Deno.connect({ + port: Number(remotePort), + hostname: serverAddress, + }); + const result = request.body?.pipeThrough( + new TransformStream({ + transform(chunk, controller) { + console.log('-- transform--'); + controller.enqueue(chunk); + }, + }) + ); + const proxyResp = result?.pipeThrough(connection); + for await (let chunk of connection.readable) { + console.log('-------', new TextDecoder().decode(chunk)); + } + // let timer: number | undefined = undefined; + // const body = new ReadableStream({ + // start(controller) { + // timer = setInterval(() => { + // const message = `It is ${new Date().toISOString()}\n`; + // controller.enqueue(new TextEncoder().encode(message)); + // }, 1000); + // }, + // pull(chunk) {}, + // cancel() { + // if (timer !== undefined) { + // clearInterval(timer); + // } + // }, + // }); + return new Response('111', { + status: 200, + headers: {}, + }); +}; + +serve(handler, { port: 8080, hostname: '0.0.0.0' }); diff --git a/apps/deno-bypass/src/bypass.ts b/apps/deno-bypass/src/bypass.ts index 62cf412..7975845 100644 --- a/apps/deno-bypass/src/bypass.ts +++ b/apps/deno-bypass/src/bypass.ts @@ -1,4 +1,4 @@ -import { serve } from 'https://deno.land/std@0.157.0/http/server.ts'; +import { serve } from 'https://deno.land/std@0.167.0/http/server.ts'; const userID = Deno.env.get('UUID'); @@ -34,7 +34,48 @@ ${userID ? 'has UUID env' : 'no UUID env'} port: Number(remotePort), hostname: serverAddress, }); + + // connection.write( + // new TextEncoder().encode('GET http://www.baidu.com/ HTTP/1.1\r\n') + // ); + // 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( + // 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, { status: 200, headers: {}, diff --git a/apps/edge-bypass-client/project.json b/apps/edge-bypass-client/project.json index 24fce16..564a778 100644 --- a/apps/edge-bypass-client/project.json +++ b/apps/edge-bypass-client/project.json @@ -32,7 +32,12 @@ "serve": { "executor": "@nrwl/js:node", "options": { - "buildTarget": "edge-bypass-client:build" + "buildTarget": "edge-bypass-client:build", + "args": [ + "run", + "--config", + "./apps/edge-bypass-client/src/assets/config-local.json" + ] }, "configurations": { "production": { @@ -43,7 +48,7 @@ "pkg": { "executor": "nx:run-commands", "options": { - "command": "npx pkg dist/apps/edge-bypass-client/main.js --targets linux,linux-arm64,macos,win --compress GZip --output dist/pkg/edge-tunnel-client/edgetunnel" + "command": "npx pkg dist/apps/edge-bypass-client/main.js --targets linux,linux-arm64,macos,win --compress GZip --output dist/pkg/edge-tunnel-client/edgetunnel" }, "dependsOn": ["build"] }, diff --git a/apps/edge-bypass-client/src/helper.ts b/apps/edge-bypass-client/src/helper.ts deleted file mode 100644 index 7d94fcf..0000000 --- a/apps/edge-bypass-client/src/helper.ts +++ /dev/null @@ -1,9 +0,0 @@ -async function* concatStreams(readables) { - for (const readable of readables) { - for await (const chunk of readable) { - yield chunk; - } - } -} - -export { concatStreams }; diff --git a/apps/edge-bypass-client/src/lib/cmd.ts b/apps/edge-bypass-client/src/lib/cmd.ts new file mode 100644 index 0000000..803e91e --- /dev/null +++ b/apps/edge-bypass-client/src/lib/cmd.ts @@ -0,0 +1,58 @@ +import { Command } from 'commander'; +import { writeFileSync, existsSync, readFileSync } from 'fs'; +import { exit } from 'node:process'; +let config: { + port: string; + address: string; + uuid: string; + config: string; +} = null; +const program = new Command(); +program.option('-v').action((options) => { + console.log(options); + exit(); +}); +program + .command('run') + .description('launch local http proxy for edge pass') + .option( + '--config ', + 'address of remote proxy, etc https://***.deno.dev/' + ) + .option( + '--address
', + 'address of remote proxy, etc https://***.deno.dev/' + ) + .option('--port ', 'local port of http proxy proxy') + .option('--uuid ', 'uuid') + .option('--save', 'if this is pass, will save to config.json') + .action((options) => { + console.log(__dirname); + console.log(process.cwd()); + if (options.config) { + if (existsSync(options.config)) { + const content = readFileSync(options.config, { + encoding: 'utf-8', + }); + config = JSON.parse(content); + return; + } else { + console.error('config not exsit!'); + exit(); + } + } + config = options; + if (config.address && config.port && config.uuid) { + if (options.save) { + writeFileSync('./config.json', JSON.stringify(options), { + encoding: 'utf-8', + }); + } + } else { + console.error('need pass all args!'); + exit(); + } + }); +program.parse(); + +export { config }; diff --git a/apps/edge-bypass-client/src/lib/helper.ts b/apps/edge-bypass-client/src/lib/helper.ts new file mode 100644 index 0000000..01d08e1 --- /dev/null +++ b/apps/edge-bypass-client/src/lib/helper.ts @@ -0,0 +1,46 @@ +import { IncomingMessage } from 'http'; +import * as os from 'os'; +import * as url from 'node:url'; + +async function* concatStreams(readables: any[]) { + for (const readable of readables) { + for await (const chunk of readable) { + yield chunk; + } + } +} + +// POST http://zizi.press/test11.ttt?test1=66 HTTP/1.1 +// Host: zizi.press +// User-Agent: curl/7.83.1 +// Connection: Keep-Alive +// Content-Type: application/json +// Accept: application/json +// Content-Length: 16 + +// {"tool": "curl"} + +//------------------------------------- +// GET http://zizi.press/test11.ttt?test1=66 HTTP/1.1 +// Host: zizi.press +// User-Agent: curl/7.83.1 +// Accept: */* +// Connection: Keep-Alive + +function rawHTTPHeader(req: IncomingMessage) { + const reqUrl = url.parse(req.url); + const headers = Object.entries(req.headers) + .map(([key, value]) => { + return `${key}:${value}`; + }) + .join(os.EOL); + const raw = `${req.method} ${reqUrl.path} HTTP/${req.httpVersion}${os.EOL}${headers}${os.EOL}${os.EOL}`; + return raw; +} + +function rawHTTPPackage(req: IncomingMessage) { + const rawHttpHeader = rawHTTPHeader(req); + return concatStreams([[rawHttpHeader], req]); +} + +export { concatStreams, rawHTTPPackage, rawHTTPHeader }; diff --git a/apps/edge-bypass-client/src/lib/http-handler.ts b/apps/edge-bypass-client/src/lib/http-handler.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/edge-bypass-client/src/main copy 2.ts b/apps/edge-bypass-client/src/main copy 2.ts index c287cb9..163adcd 100644 --- a/apps/edge-bypass-client/src/main copy 2.ts +++ b/apps/edge-bypass-client/src/main copy 2.ts @@ -7,7 +7,7 @@ import { writeFileSync, existsSync, readFileSync } from 'fs'; import { exit } from 'node:process'; import * as url from 'node:url'; import * as undici from 'undici'; -import { concatStreams } from './helper'; +import { concatStreams } from './lib/helper'; let config: { port: string; diff --git a/apps/edge-bypass-client/src/main copy 3.ts b/apps/edge-bypass-client/src/main copy 3.ts index fda5832..322d736 100644 --- a/apps/edge-bypass-client/src/main copy 3.ts +++ b/apps/edge-bypass-client/src/main copy 3.ts @@ -7,7 +7,7 @@ import { writeFileSync, existsSync, readFileSync } from 'fs'; import { exit } from 'node:process'; import * as url from 'node:url'; import * as undici from 'undici'; -import { concatStreams } from './helper'; +import { concatStreams } from './lib/helper'; import * as http from 'node:http'; let config: { diff --git a/apps/edge-bypass-client/src/main copy 4.ts b/apps/edge-bypass-client/src/main copy 4.ts index fda5832..d5d82a4 100644 --- a/apps/edge-bypass-client/src/main copy 4.ts +++ b/apps/edge-bypass-client/src/main copy 4.ts @@ -7,7 +7,7 @@ import { writeFileSync, existsSync, readFileSync } from 'fs'; import { exit } from 'node:process'; import * as url from 'node:url'; import * as undici from 'undici'; -import { concatStreams } from './helper'; +import { concatStreams } from './lib/helper'; import * as http from 'node:http'; let config: { @@ -73,6 +73,8 @@ httpProxyServer.on('connect', async (req, clientSocket, head) => { `HTTP/${req.httpVersion} 200 Connection Established\r\n\r\n` ); + // make call to edge http server + // 1. forward all package remote, socket over http body const { body, headers, statusCode, trailers } = await undici.request( config.address, { @@ -87,6 +89,7 @@ httpProxyServer.on('connect', async (req, clientSocket, head) => { } ); + // 2. forward remote reponse body to clientSocket body.pipe(clientSocket).on('error', (error) => { console.log('serever reponse to clientSocket: ' + error); }); diff --git a/apps/edge-bypass-client/src/main.ts b/apps/edge-bypass-client/src/main.ts index 0904ca4..e6688d8 100644 --- a/apps/edge-bypass-client/src/main.ts +++ b/apps/edge-bypass-client/src/main.ts @@ -1,136 +1,155 @@ -import { createServer, Socket } from 'node:net'; -import { Duplex } from 'node:stream'; +import { Socket } from 'node:net'; +import { createServer } from 'node:http'; +import { Duplex, pipeline, Readable } from 'node:stream'; import { fetch } from 'undici'; import { ReadableStream, WritableStream } from 'node:stream/web'; import { Command } from 'commander'; import { writeFileSync, existsSync, readFileSync } from 'fs'; import { exit } from 'node:process'; +import { config } from './lib/cmd'; +import * as url from 'node:url'; +import * as undici from 'undici'; +import { concatStreams, rawHTTPHeader, rawHTTPPackage } from './lib/helper'; -let config: { - port: string; - address: string; - uuid: string; - config: string; -} = null; -const program = new Command(); -program - .command('run') - .description('launch local http proxy for edge pass') - .option( - '--config ', - 'address of remote proxy, etc https://***.deno.dev/' - ) - .option( - '--address
', - 'address of remote proxy, etc https://***.deno.dev/' - ) - .option('--port ', 'local port of http proxy proxy', '8134') - .option('--uuid ', 'uuid') - .option('--save', 'if this is pass, will save to config.json') - .action((options) => { - if (options.config) { - if (existsSync(options.config)) { - const content = readFileSync(options.config, { - encoding: 'utf-8', - }); - config = JSON.parse(content); - return; - } else { - console.error('config not exsit!'); - exit(); - } - } - config = options; - if (options.save) { - writeFileSync('./config.json', JSON.stringify(options), { - encoding: 'utf-8', - }); - } - }); -program.parse(); +const httpProxyServer = createServer(async (req, resp) => { + const reqUrl = url.parse(req.url); + const clientSocketLoggerInfo = `[proxy to ${req.url}(http)]`; + try { + console.log( + `Client Connected To Proxy, client http version is ${req.httpVersion}, ${clientSocketLoggerInfo}}` + ); -const server = createServer(); -server.on('connection', (clientToProxySocket: Socket) => { - console.log('Client Connected To Proxy'); - // We need only the data once, the starting packet - clientToProxySocket.once('data', async (data) => { - // If you want to see the packet uncomment below - // console.log(data.toString()); - let isTLSConnection = data.toString().indexOf('CONNECT') !== -1; - let serverPort = '80'; - let serverAddress: string; - if (isTLSConnection) { - // Port changed if connection is TLS - serverPort = data - .toString() - .split('CONNECT ')[1] - .split(' ')[0] - .split(':')[1]; - serverAddress = data - .toString() - .split('CONNECT ')[1] - .split(' ')[0] - .split(':')[0]; - } else { - serverAddress = data.toString().split('Host: ')[1].split('\r\n')[0]; - } - - const { - readable: clientToProxySocketReadable, - writable: clientToProxySocketWritable, - } = Duplex.toWeb(clientToProxySocket) as any as { - readable: ReadableStream; - writable: WritableStream; - }; - - // console.log(serverAddress); - if (isTLSConnection) { - clientToProxySocket.write('HTTP/1.1 200 OK\r\n\n'); - } else { - // TODO - // proxyToServerSocket.write(data); - } - - fetch(config.address, { - headers: { - 'x-host': serverAddress, - 'x-port': serverPort, - 'x-uuid': config.uuid, - // "Content-Type": "text/plain", + 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); }, - method: 'POST', - // body: Uint8Array.from(chunks), - body: clientToProxySocketReadable, - duplex: 'half', - }) - .then((resp) => { - console.log( - `proxy to ${serverAddress}:${serverPort} and remote return ${resp.status}` - ); - resp.body.pipeTo(clientToProxySocketWritable).catch((error) => { - console.error('pipe to', JSON.stringify(error)); - }); - }) - .catch((error) => { - console.log('fetch error', error); - }); - clientToProxySocket.on('error', (err) => { - console.log('CLIENT TO PROXY ERROR'); - console.log(err); }); - }); + + // make call to edge http server + // 1. forward all package remote, socket over http body + const { body, headers, statusCode, trailers } = await undici.request( + config.address, + { + headers: { + 'x-host': reqUrl.hostname, + 'x-port': reqUrl.port || '80', + 'x-uuid': config.uuid, + // "Content-Type": "text/plain", + }, + method: 'POST', + // body: Readable.from(rawHTTPPackage(req)), + body: readableStream, + + // body: rawHTTPHeader(req), + // body: req, + } + ); + // for await (const item of rawHTTPPackage(req)) { + // myReadable.push(item); + // } + // console.log(headers, statusCode); + // for await (let chunk of body) { + // console.log(chunk.toString()); + // } + // 2. forward remote reponse body to clientSocket + + pipeline(body, resp, (error) => { + console.log( + `${clientSocketLoggerInfo} remote server to clientSocket has error: ` + + error + ); + resp.destroy(); + }); + body.on('error', (err) => { + console.log('body error', err); + }); + body.on('data', () => { + if (!readableStream.closed) { + readableStream.push(null); + } + }); + } catch (error) { + resp.destroy(); + console.log('${clientSocketLogger} has error ', error); + } }); -server.on('error', (err) => { +// handle https website +httpProxyServer.on('connect', async (req, clientSocket, head) => { + const reqUrl = url.parse('https://' + req.url); + const clientSocketLoggerInfo = `[proxy to ${req.url}]`; + try { + console.log( + `Client Connected To Proxy, client http version is ${ + req.httpVersion + }, ${clientSocketLoggerInfo}, head is ${head.toString()}` + ); + // We need only the data once, the starting packet, per http proxy spec + clientSocket.write( + `HTTP/${req.httpVersion} 200 Connection Established\r\n\r\n` + ); + + console.log(config); + // make call to edge http server + // 1. forward all package remote, socket over http body + const { body, headers, statusCode, trailers } = await undici.request( + config.address, + { + headers: { + 'x-host': reqUrl.hostname, + 'x-port': reqUrl.port, + 'x-uuid': config.uuid, + // "Content-Type": "text/plain", + }, + method: 'POST', + body: Readable.from(concatStreams([head, clientSocket])), + } + ); + console.log(`${clientSocketLoggerInfo} remote server return ${statusCode}`); + // 2. forward remote reponse body to clientSocket + pipeline(body, clientSocket, (error) => { + console.log( + `${clientSocketLoggerInfo} remote server to clientSocket has error: `, + error + ); + body?.destroy(); + clientSocket.destroy(); + }); + clientSocket.on('error', (e) => { + body?.destroy(); + clientSocket.destroy(); + console.log(`${clientSocketLoggerInfo} clientSocket has error: ` + e); + }); + clientSocket.on('end', () => { + console.log(`${clientSocketLoggerInfo} has done and end.`); + }); + } catch (error) { + clientSocket.destroy(); + console.log(`${clientSocketLoggerInfo} has error `, error); + } +}); + +httpProxyServer.on('error', (err) => { console.log('SERVER ERROR'); console.log(err); throw err; }); +httpProxyServer.on('clientError', (err, clientSocket) => { + console.log('client error: ' + err); + clientSocket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); +}); -server.on('close', () => { +httpProxyServer.on('close', () => { console.log('Client Disconnected'); }); -server.listen(Number(config.port), () => { +httpProxyServer.listen(Number(config.port), () => { console.log('Server runnig at http://localhost:' + config.port); }); diff --git a/apps/edge-bypass-client/src/proxy.js b/apps/edge-bypass-client/src/proxy.js index 8999c6c..12f6ff3 100644 --- a/apps/edge-bypass-client/src/proxy.js +++ b/apps/edge-bypass-client/src/proxy.js @@ -7,6 +7,7 @@ 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 = { diff --git a/test.mjs b/test.mjs new file mode 100644 index 0000000..1385647 --- /dev/null +++ b/test.mjs @@ -0,0 +1,4 @@ +import { Readable } from 'stream'; +const readableStream = new Readable(); +readableStream.push('ping!'); +readableStream.push('pong!'); diff --git a/text.txt b/text.txt new file mode 100644 index 0000000..943583e --- /dev/null +++ b/text.txt @@ -0,0 +1,4 @@ +GET http://zizi.press:4009 User-Agent: curl/7.83.1 Accept: */*/ HTTP/1.1 +Host: zizi.press:4009 User-Agent: curl/7.83.1 Accept: */* +Proxy-Connection: Keep-Alive +