说明

本地服务注册,基于子域名->端口映射。公网测试请开启二级或三级域名泛解析

无心跳保活、无多线程并发处理

服务器端

  • 请求ID基于全局变量,不支持PM2多进程开服务端。(多开请修改uid函数,增加网卡以及进程信息)
  • 该代码包含HTTP代理和内网穿透服务端
const net = require('net');
var nat = {
config: {servPort: 39001,natPort: 80,proxy:39002},
registry: {},//服务注册表
request: {},//请求连接
uid(pre = '', howMany = 1, len = 4) {
if (howMany > 9999) return false;
var date = new Date();
var str = date.getFullYear();
str += ('0' + (date.getMonth() + 1)).slice(-2);
str += ('0' + date.getDate()).slice(-2);
str += ('0' + date.getHours()).slice(-2);
str += ('0' + date.getMinutes()).slice(-2);
str += ('0' + date.getSeconds()).slice(-2);
if (!this.num || this.num.time !== str)
this.num = { time: str, counter: 0 }
let bd = [];
for (var i = 0; i < howMany; i++) {
this.num.counter++;
var buffNum = (Array(len).join(0) + this.num.counter).slice(len * (-1));
bd.push(pre + str + buffNum);
}
return bd.length > 1 ? bd : bd[0];
},
run(){
this.nat()
this.proxy()
},
/*************** TCP内网穿透 ***************/
nat() {
//创建主服务器
var that = this;
net.Server(async socket => {
socket.on('data',data=>{
socket.removeAllListeners('data');
try {data=JSON.parse(data)} catch (err) {return false}
if (data.act == 'regist') {
data.name.forEach(item => {
that.registry[item] = { client: socket }
})
socket.NatNames=data.name
socket.on('close',had_error=>{
socket.NatNames.forEach(item=>{
delete that.registry[item]
})
})
} else if (data.act == 'connect') {
var request=that.request[data.key]
if(request&&request.data&&request.socket){
request.socket.pipe(socket).pipe(request.socket)
socket.write(request.data)
request.socket.on('error',err=>{request.socket.end();socket.end()})
socket.on('error',err=>{request.socket.end();socket.end()})
}
}
})
socket.on('error',(err)=>{socket.end();console.log(`${new Date().toLocaleString()}: ${err.message}`)}) }).listen(that.config.servPort,()=>{console.log(`NAR serv running at ${that.config.servPort}`)})
//创建代理通信服务器
net.Server(socket => {
var key = this.uid('RC')
that.request[key] = {socket}
socket.on('data', data => {
socket.removeAllListeners('data');
that.request[key].data=data
var domain = data.toString().match(/Host: (\S)+/i)[0].split(':')[1]
var nat = domain.split('.')[0].trim()
if (that.registry[nat]){
var str=JSON.stringify({ act: 'connect', key, nat})
that.registry[nat].client.write(str)
}
})
}).listen(that.config.natPort,()=>{console.log(`NAR http running at ${that.config.natPort}`)})
},
/*************** http[s]代理 ***************/
proxy(){
var that=this
const net = require("net")
net.Server((client) => {
client.on("data", (data) => {
let req = that.reqParse(data)
if (!req) return;
let server = net.connect(req.port, req.host);
client.removeAllListeners('data');
client.pipe(server).pipe(client);
server.on('error',err=>{client.end(),server.end()})
client.on('error',err=>{client.end(),server.end()})
if (req.method == 'CONNECT')
client.write("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n");
else
server.write(req.buffer);
})
}).listen(this.config.proxy, () => {console.log("prox running at port: " + this.config.proxy)})
},
/*************** 格式化http请求头 ***************/
reqParse(b) {
let s = b.toString('utf8');
let method = s.split('\n')[0].match(/^([A-Z]+)\s/)[1];
if (method == 'CONNECT') {
var arr = s.match(/^([A-Z]+)\s([^\:\s]+)\:(\d+)\sHTTP\/(\d\.\d)/);
if (arr && arr[1] && arr[2] && arr[3] && arr[4])
return {
method: arr[1],
host: arr[2],
port: arr[3],
httpVersion: arr[4],
buffer: b
};
} else {
let arr = s.match(/^([A-Z]+)\s([^\s]+)\sHTTP\/(\d\.\d)/);
let index = 0;
//请求头,请求体处理
for (var i = 0, len = b.length - 3; i < len; i++) {
if (b[i] == 0x0d && b[i + 1] == 0x0a && b[i + 2] == 0x0d && b[i + 3] == 0x0a) {
index = i + 4;
}
}
if (!index) return false;
var header = b.slice(0, index).toString('utf8');
//替换connection头
header = header.replace(/(proxy\-)?connection\:.+\r\n/ig, '')
.replace(/Keep\-Alive\:.+\r\n/i, '')
.replace("\r\n", '\r\nConnection: close\r\n');
//替换网址格式(去掉域名部分)
if (arr[1] == '1.1') {
var url = arr[2].replace(/http\:\/\/[^\/]+/, '');
if (arr[2] != url) header = header.replace(arr[2], url);
}
b = Buffer.concat([Buffer.from(header), b.slice(index)]); if (arr && arr[1] && arr[2] && arr[3]) {
var host = s.match(/Host\:\s+([^\n\s\r]+)/)[1];
if (host) {
var _p = host.split(':', 2);
return {
method: arr[1],
host: _p[0],
port: _p[1] ? _p[1] : 80,
path: arr[2],
httpVersion: arr[3],
buffer: b
};
}
}
}
return false;
}
}
nat.run()

本地客户端

const HTTP=require('http')
const FS = require('fs')
const net = require('net');
//内网穿透客户端
var nat = {
config: {
serv: {host: '47.92.29.253',port: 39001},//服务器配置
local: {api: 80,md:81},//本地服务名=>端口映射
retry: 300000,//异常重连
},
connServ() {
var that = this
let serv =this.serv= net.connect(that.config.serv, () => {
console.log(`${new Date().toLocaleString()}: NAT已连接服务器`)
var option = { act: 'regist', name: Object.keys(that.config.local) }
serv.write(JSON.stringify(option))
})
serv.setKeepAlive(true)
serv.on('data', data => {
try { data = JSON.parse(data) } catch (err) { return false }
if (data.act == 'connect') {
var request = net.connect(that.config.serv, () => {
var response = net.connect({ host: '127.0.0.1', port: that.config.local[data.nat] },()=>{
response.pipe(request).pipe(response)
request.write(JSON.stringify({ act: 'connect', key: data.key }))
})
request.on('error',err=>{response.end();request.end()})
response.on('error',err=>{response.end();request.end()})
})
}
})
serv.on('error', err => {
console.log(`${new Date().toLocaleString()}: NAT服务器连接异常:${err.message}`)
serv.end();
that.connServ()
})
serv.on('end', () => {
console.log(`${new Date().toLocaleString()}: NAT服务器连接已断开`)
serv.end();
that.connServ()
})
},
//定时监测连接状态&&断线重连
run(){
var that = this
that.connServ()
setInterval(() => {
if (that.serv.readyState != 'open') {
that.serv.end()
that.connServ()
}
}, that.config.retry)
}
}


文章转载自 http://www.dtmao.cc/news_show_113319.shtml

nodejs内网穿透的更多相关文章

  1. 旧手机改造成web服务器并实现内网穿透

    前几天由于gitee的审核引擎一通乱杀,使得gitee pages停止提供服务,心生更换服务器或者其他pages托管的想法,看了看价格感人的云服务器以及空空的钱包,这时,脑子有个奇怪的想法飘过,自己搞 ...

  2. 内网穿透神器(ngrok)服务端部署【分享一台自己的ngrok服务器】【多平台】

    Ngrok为何物 “ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道.ngrok 可捕获和分析所有通道上的流量,便于后期分析和重放.”这是百度百科上给Ng ...

  3. SSH 端口转发+内网穿透

    用最直白的语言对本文所有内容进行定义: 端口转发(-L):用A机器(内网)登录B机器(公网), 在A机器打开端口,将收到的所有请求转发到B机器的某个端口 (在代理机上执行) 内网穿透(-R):用A机器 ...

  4. Ngrok 内网穿透利器

    Ngrok是什么 Ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道.Ngrok 可捕获和分析所有通道上的流量,便于后期分析和重放 为什么要使用Ngrok ...

  5. Ngrok 内网穿透神器(转载)

    mac上配置web服务: http://note.rpsh.net/posts/2013/11/27/osx-10-9-apache-server-php-mysql/ Ngrok 内网穿透神器 由于 ...

  6. SSH的内网穿透

    SSH的内网穿透 1.内网:     ssh -N -f -R 2222:127.0.0.1:22 lienzh@我的PC的IP2.外网:     ssh -p 2222 root@localhost ...

  7. ngrok内网穿透(微信调试:只试用于微信测试账号)

    一.简介 ngrok:https://ngrok.com 功能:就是把外网地址映射到本地的内网地址 缺点: 1.免费版生成的域名是随机的(由于我是用于调试,就没什么关系,如果是正式生产环境可能需要一个 ...

  8. 内网穿透&UDP打洞

    这两天找度度重新回忆了一下关于内网穿透的事情,在百度文库上找到了两三篇写的比较通俗易懂的文章,把内网穿透做个简单总结. 首先文章建议 Cone NAPT 还有希望,要是 Symmetri NAPT 就 ...

  9. ngrok内网穿透神器

    ngrok类似国内的花生壳,可以将本地的内网映射到公网上,这样就可以做web开发,微信开发了.下面就介绍下ngrok是怎么配置的吧. 方式一: 一.打开ngrok的官网https://ngrok.co ...

随机推荐

  1. 01-docker基本使用

    docker 常用命令 指令 说明 docker images 查看已经下载的镜像 docker rmi 镜像名称:标签名 删除已经下载的镜像 docker search 镜像 从官方仓库中查看镜像 ...

  2. clang-format 数组初始化,多行模式

    clang-format 在格式化多行数组的初始化时不够理想.例如 int array[] = { 0, 1, 2 }; 会被格式化为: int array[] = { 0, 1, 2}; 如果在最后 ...

  3. 个人微信公众号搭建Python实现 -个人公众号搭建-被动回复消息建模(14.3.2)

    @ 目录 1.阅读官方文档 2.思考 关于作者 1.阅读官方文档 点击进入微信官方开发者文档 接收普通消息 文本消息 图片消息 语言消息 视频消息 小视频消息 地理位置消息 链接消息 接收事件消息 关 ...

  4. angular 8 表单带文件上传接口

    <div id="homework"> <form (ngSubmit)="doSubmit()" enctype="multipa ...

  5. Spring Boot 2.4版本前后的分组配置变化及对多环境配置结构的影响

    前几天在<Spring Boot 2.4 对多环境配置的支持更改>一文中,给大家讲解了Spring Boot 2.4版本对多环境配置的配置变化.除此之外,还有一些其他配置变化,所以今天我们 ...

  6. Protobuf简单类型直接反序列化方法

    我有一个想法,有一个能够进行跨平台的高性能数据协议规范,能够让数据在两个不同的程序之间进行读取,最好能够支持直接将object序列化,那就完美了. 目标 支持任意Object序列化 支持从类似Syst ...

  7. Mongodb分布式集群副本集+分片

    目录 简介 1. 副本集 1.1 MongoDB选举的原理 1.2 复制过程 2. 分片技术 2.1 角色 2.2 分片的片键 2.3 片键分类 环境介绍 1.获取软件包 2.创建路由.配置.分片等的 ...

  8. .NET Core AWS S3云存储

    前言 最近有需要用到AWS S3云存储上传附件,这里对利用.NET或.NET Core在调用SDK APi需要注意的一点小问题做个记录,或许能对后续有用到的童鞋提供一点帮助 AWS S3云存储 官方已 ...

  9. App Shortcuts 快捷方式:Android 的 '3D Touch'

    Hello Shortcuts 从Android7.1(API level25)开始,开发者可以为自己的app定制shortcuts.shortcuts使用户更便捷.快速的使用app.我个人感觉有点像 ...

  10. Idea创建Maven项目时,没有自动添加Artifacts

    可能的原因是没有进行更新,因为第一次创建时由于要下载东西,所以pom文件是自动改变的,如果没有设置自动更新maven项目,就可能出现这种情况 这时候只要去maven project中点击一下更新按钮, ...