说明

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

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

服务器端

  • 请求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. 手写开源ORM框架介绍

    手写开源ORM框架介绍 简介 前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架.一直没有时间去补充相应的文档,现在正好抽时间去整理下.通过思路历程和代码注释,一方面重温下知识,另 ...

  2. git 上传 + 分支

    Git   上传 创建本地文件夹,更改为项目仓库(test) (1) 新建文件夹   test (2) 打开文件夹   git init 把项目添加到仓库内(test) (1) 复制项目到文件夹内 ( ...

  3. 5分钟完全掌握Python协程

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 1. 协程相关的概念 1.1 进程和线程 进程(Process)是应用程序启动的实例,拥有代码.数据 ...

  4. 写一个简单的HTML留言板

    最近有点懒,没码什么字,防止遗忘,从头开始码,写一个简单的HTML留言板.包含两个文件,book.html还有style.css,放在同一目录下. book.html 1 <!DOCTYPE h ...

  5. 深入理解Spring Security授权机制原理

    原创/朱季谦 在Spring Security权限框架里,若要对后端http接口实现权限授权控制,有两种实现方式. 一.一种是基于注解方法级的鉴权,其中,注解方式又有@Secured和@PreAuth ...

  6. Flutter低版本迁移到高版本

    记录一次Flutter v1.12.13+hotfix.8 升级到1.22.4(当前最新) 后 ,旧项目的适配过程 工具:Android Studio 4.1.1 1.android 的MainAct ...

  7. SecureCRT SSH Linux中不显示彩色 字体颜色、文件夹和文件显示的颜色区别开解决办法

    SecureCRT SSH Linux中不显示彩色 字体颜色.文件夹和文件显示的颜色区别开解决办法 实验环境: 刚开始我的情况是这样的:带颜色的显示不出来,然后还能看到,此处有内容,猜测是Secure ...

  8. git 清除本地git commit的内容

    由于我经常git add . , 然后再git commit -m "文字说明",这样有时候代码嵌套再另一个项目里面,就会把外面的项目一起提交了,导致提交的代码不是我想要的.小菜鸟 ...

  9. Atlas 2.1.0 实践(2)—— 安装Atlas

    在完成Atlas编译以后,就可以进行Atlas的安装了.Atlas的安装主要是安装Atlas的Server端,也就Atlas的管理页面,并确保Atlas与Kafka Hbase Solr等组件的集成. ...

  10. Vue知识点精简汇总

    一. 组件component 1. 什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码组件是自定义元素(对象) 2. 定义组件的 ...