GVKun编程网logo

Nodejs实现内网穿透服务(nodejs 内网穿透)

26

以上就是给各位分享Nodejs实现内网穿透服务,其中也会对nodejs内网穿透进行解释,同时本文还将给你拓展Angrok一个内网穿透服务、docker云部署frp实现内网穿透,访问内网主机、frp+n

以上就是给各位分享Nodejs实现内网穿透服务,其中也会对nodejs 内网穿透进行解释,同时本文还将给你拓展Angrok 一个内网穿透服务、docker云部署frp实现内网穿透,访问内网主机、frp + nginx 配置多人共用的http 内网穿透服务、frp 和 nginx 搭建一个内网穿透服务器等相关知识,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

本文目录一览:

Nodejs实现内网穿透服务(nodejs 内网穿透)

Nodejs实现内网穿透服务(nodejs 内网穿透)

也许你很难从网上找到一篇从代码层面讲解内网穿透的文章,我曾搜过,未果,遂成此文。

1. 局域网内代理

我们先来回顾上篇,如何实现一个局域网内的服务代理?因为这个非常简单,所以,直接上代码。

const net = require(''net'')

const proxy = net.createServer(socket => {
  const localServe = new net.Socket()
  localServe.connect(5502, ''192.168.31.130'') // 局域网内的服务端口及ip。

  socket.pipe(localServe).pipe(socket)
})

proxy.listen(80)

这就是一个非常简单的服务端代理,代码简单清晰明了,如果有疑问的话,估计就是管道(pipe)这里,简单说下。socket是一个全双工流,也就是既可读又可写的数据流。代码中,当socket接收到客户端数据的时候,它会把数据写入localSever,当localSever有数据的时候,它会把数据写入socket,socket再把数据发送给客户端。

2. 内网穿透

局域网代理简单,内网穿透就没这么简单了,但是,它却是核心的代码,需要在其上做相当的逻辑处理。具体实现之前,我们先梳理一下内网穿透。

什么是内网穿透?

简单来说,就是公网客户端,可以访问局域网内的服务。比如,本地启动的服务。公网客户端怎么会知道本地启的serve呢?这里必然要借助公网服务端。那么公网服务端又怎么知道本地服务呢?这就需要本地和服务端建立socket链接了。

四个角色

通过上面的描述,我们引出四个角色。

  1. 公网客户端,我们取名叫client。
  2. 公网服务端,因为有代理的作用,我们取名叫proxyServe。
  3. 本地服务,取名localServe。
  4. 本地与服务端的socket长连接,它是proxyServe与localServe之前的桥梁,负责数据的中转,我们取名叫bridge。

其中,client和localServe不需要我们关心,因为client可以是浏览器或者其它,localServe就是一个普通的本地服务。我们只需要关心proxyServe和bridge就可以了。我们这里介绍的依然是最简单的实现方式,提供一种思路与思考,那我们先从最简单的开始。

bridge

我们从四个角色一节知道, bridge是一个与proxyServe之间socket连接,且是数据的中转,上代码捋捋思路。

const net = require(''net'')

const proxyServe = ''10.253.107.245''

const bridge = new net.Socket()
bridge.connect(80, proxyServe, _ => {
  bridge.write(''GET /regester?key=sq HTTP/1.1\r\n\r\n'')
})

bridge.on(''data'', data => {
  const localServer = new net.Socket()
  localServer.connect(8088, ''localhost'', _ => {
    localServer.write(data)
    localServer.on(''data'', res => bridge.write(res))
  })
})

代码清晰可读,甚至朗朗上口。引入net库,声明公网地址,创建bridge,使bridge连接proxyServe,成功之后,向proxyServe注册本地服务,接着,bridge监听数据,有请求到达时,创建与本地服务的连接,成功之后,把请求数据发送给localServe,同时监听响应数据,把响应流写入到bridge。

其余没什么好解释的了,毕竟这只是示例代码。不过示例代码中有段/regester?key=sq,这个key可是有大作用的,在这里key=sq。那么角色client通过代理服务访问本地服务的是,需要在路径上加上这个key,proxyServe才能对应的上bridge,从而对应上localServe。

例如:lcoalServe是:http://localhost:8088 ,rpoxyServe是example.com ,注册的key是sq。那么要想通过prxoyServe访问到localServe,需要如下写法:example.com/sq 。为什么要这样写?当然只是一个定义而已,你读懂这篇文章的代码之后,可以修改这样的约定。

那么,且看以下关键代码:

proxyServe

这里的proxyServe虽然是一个简化后的示例代码,讲起来依然有些复杂,要想彻底弄懂,并结合自己的业务做成可用代码,是要下一番功夫的。这里我把代码拆分成一块一块,试着把它讲明白,我们给代码块取个名字,方便讲解。
代码块一:createServe

该块的主要功能是创建代理服务,与client和bridge建立socket链接,socket监听数据请求,在回调函数里做逻辑处理,具体代码如下:

const net = require(''net'')

const bridges = {} // 当有bridge建立socket连接时,缓存在这里
const clients = {} // 当有client建立socket连接时,缓存在这里,具体数据结构看源代码

net.createServer(socket => {
  socket.on(''data'', data => {
    const request = data.toString()
    const url = request.match(/.+ (?<url>.+) /)?.groups?.url
    
    if (!url) return

    if (isBridge(url)) {
      regesterBridge(socket, url)
      return
    }

    const { bridge, key } = findBridge(request, url)
    if (!bridge) return

    cacheClientRequest(bridge, key, socket, request, url)

    sendRequestToBridgeByKey(key)
  })
}).listen(80)

看一下数据监听里的代码逻辑:

  1. 把请求数据转换成字符串。
  2. 从请求里查找URL,找不到URL直接结束本次请求。
  3. 通过URL判断是不是bridge,如果是,注册这个bridge,否者,认为是一个client请求。
  4. 查看client请求有没有已经注册过的bridge -- 记住,这是一个代理服务,没有已经注册的bridge,就认为请求无效。
  5. 缓存这次请求。
  6. 接着再把请求发送给bridge。

结合代码及逻辑梳理,应该能看得懂,但是,对5或许有疑问,接下来一一梳理。

代码块二:isBridge

判断是不是一个bridge的注册请求,这里写的很简单,不过,真实业务,或许可以定义更加确切的数据。

function isBridge (url) {
  return url.startsWith(''/regester?'')
}

代码块三:regesterBridge
简单,看代码再说明:

function regesterBridge (socket, url) {
  const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
  bridges[key] = socket
  socket.removeAllListeners(''data'')
}
  1. 通过URL查找要注册的bridge的key。
  2. 把改socket连接缓存起来。
  3. 移除bridge的数据监听 -- 代码块一里每个socket都有默认的数据监听回调函说,如果不移除,会导致后续数据混乱。

代码块四:findBridge

逻辑走到代码块4的时候,说明这已经是一个client请求了,那么,需要先找到它对应的bridge,没有bridge,就需要先注册bridge,然后需要用户稍后再发起client请求。代码如下:

function findBridge (request, url) {
  let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key
  let bridge = bridges[key]
  if (bridge) return { bridge, key }

  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer
  if (!referer) return {}

  key = referer.split(''//'')[1].split(''/'')[1]
  bridge = bridges[key]
  if (bridge) return { bridge, key }

  return {}
}

  • 从URL中匹配出要代理的bridge的key,找到就返回对应的bridge及key。
  • 找不到再从请求头里的referer里找,找到就返回bridge及key。
  • 都找不到,我们知道在代码块一里会结束掉本次请求。

代码块五:cacheClientRequest

代码执行到这里,说明已经是一个client请求了,我们先把这个请求缓存起来,缓存的时候,我们一并把请求对应的bridge、key绑定一起缓存,方便后续操作。

为什么要缓存client请求?

在目前的方案里,我们希望请求和响应都是成对有序的。我们知道网络传输都是分片传输的,目前来看,如果我们不在应用层控制请求和响应成对且有序,会导致数据包之间的混乱现象。暂且这样,后续如果有更好方案,可以不在应用层强制控制数据的请求响应有序,可以信赖tcp/ip层。
讲完原因,我们先来看缓存代码,这里比较简单,复杂的在于逐个取出请求并有序返回整个响应。

function cacheClientRequest (bridge, key, socket, request, url) {
  if (clients[key]) {
    clients[key].requests.push({bridge, key, socket, request, url})
  } else {
    clients[key] = {}
    clients[key].requests = [{bridge, key, socket, request, url}]
  }
}

我们先判断该bridge对应的key下是不是已经有client的请求缓存了,如果有,就push进去。

如果没有,我们就创建一个对象,把本次请求初始化进去。

接下来就是最复杂的,取出请求缓存,发送给bridge,监听bridge的响应,直到本次响应结束,在删除bridge的数据监听,再试着取出下一个请求,重复上面的动作,直到处理完client的所有请求。

代码块六:sendRequestToBridgeByKey

在代码块五的最后,对该块做了概括性的说明。可以先稍作理解,在看下面代码,因为代码里会有一些响应完整性的判断,去除这一些,代码就好理解一些。整个方案,我们没有对请求完整性进行处理,原因是,一个请求的基本都在一份数据包大小内,除非是文件上传接口,我们暂不处理,不然,代码又会复杂一些。

function sendRequestToBridgeByKey (key) {
  const client = clients[key]
  if (client.isSending) return

  const requests = client.requests
  if (requests.length <= 0) return

  client.isSending = true
  client.contentLength = 0
  client.received = 0

  const {bridge, socket, request, url} = requests.shift()

  const newUrl = url.replace(key, '''')
  const newRequest = request.replace(url, newUrl)

  bridge.write(newRequest)
  bridge.on(''data'', data => {
    const response = data.toString()

    let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code
    if (code) {
      code = parseInt(code)
      if (code === 200) {
        let contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength
        if (contentLength) {
          contentLength = parseInt(contentLength)
          client.contentLength = contentLength
          client.received = Buffer.from(response.split(''\r\n\r\n'')[1]).length
        }
      } else {
        socket.write(data)
        client.isSending = false
        bridge.removeAllListeners(''data'')
        sendRequestToBridgeByKey(key)
        return
      }
    } else {
      client.received += data.length
    }

    socket.write(data)

    if (client.contentLength <= client.received) {
      client.isSending = false
      bridge.removeAllListeners(''data'')
      sendRequestToBridgeByKey(key)
    }
  })
}

从clients里取出bridge key对应的client。
判断该client是不是有请求正在发送,如果有,结束执行。如果没有,继续。
判断该client下是否有请求,如果有,继续,没有,结束执行。
从队列中取出第一个,它包含请求的socket及缓存的bridge。
替换掉约定的数据,把最终的请求数据发送给bridge。
监听bridge的数据响应。

  • 获取响应code
    • 如果响应是200,我们从中获取content length,如果有,我们对本次请求做一些初始化的操作。设置请求长度,设置已经发送的请求长度。
    • 如果不是200,我们把数据发送给client,并且结束本次请求,移除本次数据监听,递归调用sendRequestToBridgeByKey
  • 如果没有获取的code,我们认为本次响应非第一次,于是,把其长度累加到已发送字段上。
  • 我们接着发送该数据到client。
  • 再判断响应的长度是否和已经发送的过的数据长度一致,如果一致,设置client的数据发送状态为false,移除数据监听,递归调用递归调用sendRequestToBridgeByKey。

至此,核心代码逻辑已经全部结束。

总结

理解这套代码之后,就可以在其上做扩展,丰富代码,为你所用。理解完这套代码,你能想到,它还有哪些使用场景吗?是不是这个思路也可以用在远程控制上,如果你要控制客户端时,从这段代码找找,是不是会有灵感。
这套代码或许会有难点,可能要对tcp/ip所有了解,也需要对http有所了解,并且知道一些关键的请求头,知道一些关键的响应信息,当然,对于http了解的越多越好。
如果有什么需要交流,欢迎留言。

proxyServe源码

const net = require(''net'')

const bridges = {}
const clients = {}

net.createServer(socket => {
  socket.on(''data'', data => {
    const request = data.toString()
    const url = request.match(/.+ (?<url>.+) /)?.groups?.url
    
    if (!url) return

    if (isBridge(url)) {
      regesterBridge(socket, url)
      return
    }

    const { bridge, key } = findBridge(request, url)
    if (!bridge) return

    cacheClientRequest(bridge, key, socket, request, url)

    sendRequestToBridgeByKey(key)
  })
}).listen(80)

function isBridge (url) {
  return url.startsWith(''/regester?'')
}

function regesterBridge (socket, url) {
  const key = url.match(/(^|&|\?)key=(?<key>[^&]*)(&|$)/)?.groups?.key
  bridges[key] = socket
  socket.removeAllListeners(''data'')
}

function findBridge (request, url) {
  let key = url.match(/\/(?<key>[^\/\?]*)(\/|\?|$)/)?.groups?.key
  let bridge = bridges[key]
  if (bridge) return { bridge, key }

  const referer = request.match(/\r\nReferer: (?<referer>.+)\r\n/)?.groups?.referer
  if (!referer) return {}

  key = referer.split(''//'')[1].split(''/'')[1]
  bridge = bridges[key]
  if (bridge) return { bridge, key }

  return {}
}

function cacheClientRequest (bridge, key, socket, request, url) {
  if (clients[key]) {
    clients[key].requests.push({bridge, key, socket, request, url})
  } else {
    clients[key] = {}
    clients[key].requests = [{bridge, key, socket, request, url}]
  }
}

function sendRequestToBridgeByKey (key) {
  const client = clients[key]
  if (client.isSending) return

  const requests = client.requests
  if (requests.length <= 0) return

  client.isSending = true
  client.contentLength = 0
  client.received = 0

  const {bridge, socket, request, url} = requests.shift()

  const newUrl = url.replace(key, '''')
  const newRequest = request.replace(url, newUrl)

  bridge.write(newRequest)
  bridge.on(''data'', data => {
    const response = data.toString()

    let code = response.match(/^HTTP[S]*\/[1-9].[0-9] (?<code>[0-9]{3}).*\r\n/)?.groups?.code
    if (code) {
      code = parseInt(code)
      if (code === 200) {
        let contentLength = response.match(/\r\nContent-Length: (?<contentLength>.+)\r\n/)?.groups?.contentLength
        if (contentLength) {
          contentLength = parseInt(contentLength)
          client.contentLength = contentLength
          client.received = Buffer.from(response.split(''\r\n\r\n'')[1]).length
        }
      } else {
        socket.write(data)
        client.isSending = false
        bridge.removeAllListeners(''data'')
        sendRequestToBridgeByKey(key)
        return
      }
    } else {
      client.received += data.length
    }

    socket.write(data)

    if (client.contentLength <= client.received) {
      client.isSending = false
      bridge.removeAllListeners(''data'')
      sendRequestToBridgeByKey(key)
    }
  })
}

到此这篇关于Nodejs实现内网穿透服务的文章就介绍到这了,更多相关Node 内网穿透内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

您可能感兴趣的文章:
  • 高性能的内网穿透工具frp使用场景
  • 详解使用内网穿透工具Ngrok代理本地服务
  • SSH端口转发实现内网穿透的实现
  • 超好用的免费内网穿透工具【永久免费不限制流量】

Angrok 一个内网穿透服务

Angrok 一个内网穿透服务

网上的搭建的教程挺多,尝试搭建的时候遇到了很多问题。

准备

# 升级yum
yum update
# 安装gcc
yum install gcc

1、GO环境安装

## 使用1.4版本,不要使用高版本。
mkdir /root/goproj
cd /root/goproj
wget https://storage.googleapis.com/golang/go1.4.1.linux-amd64.tar.gz
##  解压
###配置go 环境变量
vi /etc/profile
export GOROOT=/root/goproj/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/root/goproj/ngrok
source /etc/profile

2. ngrok下载

cd /root/goproj
git clone https://github.com/inconshreveable/ngrok.git
cd /root/goproj/ngrok

3.证书生成

openssl genrsa -out rootCA.key 2048
openssl req -x509 -new -nodes -key rootCA.key -subj "/CN=angrok.cn" -days 5000 -out rootCA.pem
openssl genrsa -out device.key 2048
openssl req -new -key device.key -subj "/CN=angrok.cn" -out device.csr
openssl x509 -req -in device.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out device.crt -days 5000

cp rootCA.pem assets/client/tls/ngrokroot.crt

 cp device.crt assets/server/tls/snakeoil.crt 

cp device.key assets/server/tls/snakeoil.key

4.下载ngrok go包(建议手动下载)

wget https://github.com/inconshreveable/go-update/archive/v0.zip
wget https://github.com/go-yaml/yaml/archive/v1.zip
#将下载的文件解压后的go文件放到
mkdir /root/goproj/ngrok/src/gopkg.in/inconshreveable/go-update.v0
mkdir /root/goproj/ngrok/src/gopkg.in/yaml.v1

1)编译server端

cd /root/goproj/ngrok
make release-server

#### 编译之前请确保已经  安装了gcc  。。。报错请(yum install gcc)

2)编译客户端

1.Windows客户端
cd /root/goproj/go/src
GOOS=windows GOARCH=amd64 ./make.bash
cd /root/goproj/ngrok
GOOS=windows GOARCH=amd64 make release-client
#同理,这里的amd64是64位系统,32位改成386
#会在 bin/windows_amd64 目录下生成ngrok客户端程序,将ngrok.exe下载到windows操作系统

2.Mac客户端

cd /root/goproj/go/src
GOOS=darwin GOARCH=amd64 ./make.bash
cd /root/goproj/ngrok
GOOS=darwin GOARCH=amd64 make release-client
#会在 bin/darwin_amd64/ 目录下生成ngrok客户端程序

5. 服务端ngrokd后台启动运行

ngrok 服务在关闭远程时候会被杀掉进程。

输入下面命令

##  安装screen神器
yum install screen
screen -S keepNgrok
/root/goproj/ngrok/bin/ngrokd -domain="angrok.cn" -httpAddr=":80" -httpsAddr=":8081" -tunnelAddr=":4443" &

6.客户端使用

#新建 ngrok.cfg 文件,内容:
server_addr: "angrok.cn:4443"
trust_host_root_certs: true



#客户端启动
./ngrok -config=ngrok.cfg -subdomain=a 8080

docker云部署frp实现内网穿透,访问内网主机

docker云部署frp实现内网穿透,访问内网主机

1、前言

1.1、配置

1、2h2g2M华为云ubunto镜像国内服务器(必须准备一个有公网ip的服务器)
image.png

2、配置docker并启动

2.1、安装docker和docker-compose

此过程前两篇文章有详细的,这里不赘述

docker -v
docker-compose -v
# 出现版本信息即安装成功
2.2、创建配置文件并修改
vim docke-compose.yml
# 将下面内容粘进去
version: ''3''
services:
  frps:
    image: docker.1panel.live/snowdreamtech/frps:0.60
    container_name: frps
    restart: always
    network_mode: host
    volumes:
      - ./frps.toml:/etc/frp/frps.toml
# 完成后键入ESC,输入英文的":"+wq+Enter,保存并退出 
# 需要在docker-compose.yml同级目录创建编辑frps.toml
vim frps.toml
# 将下面内容粘贴进去
# 客户端与服务连接端口
bindPort = 7000
# 客户端连接服务端时认证的密码
auth.token = "admin123"
# http协议监听端口
vhostHTTPPort = 28080
# web界面配置
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "admin"

完成后键入ESC,输入英文的":"+wq+Enter,保存并退出

# 执行
docker-compose up -d
# 放通服务器的7500端口,通过http://<公网ip>:7500访问后台,账号密码是上面设置的admin/admin

3、客户端配置与启动

3.1、客户端配置

这里的客户端是我的windows主机
在下面下载相应版本并解压,我这里是windows-amd64
https://github.com/fatedier/frp/releases
image.png
解压好后用编辑工具编辑frpc.toml

serverAddr = "服务器公网ip"
serverPort = 7000
auth.token = "admin123"

# 国内服务器没有ICP备案用不了域名解析的,但是可以把customDomains值换成公网ip
# 但是这样跟tcp没有区别了不如直接tcp
# 所以这一块可以把customDomains值换成公网ip即可,我这里客户端主机在127.0.0.1:9000有运行,根据你自己实际情况修改值
# 这一块的用http://<公网ip>:28080端口访问
[[proxies]]
name = "nwct"
type = "http"
localIP = "127.0.0.1"
localPort = 9000
customDomains = ["域名"]
# 如果要用域名访问,首先需要域名解析这个公网ip,其次需要通过域名:28080访问,如果要直接通过域名访问不加端口,需要添加反代,上篇文章有,这里不作为重点

# 这里我127.0.0.1:3000运行着fastgpt,这里用tcp穿透
# 这一块用http://<公网ip>:23000访问
[[proxies]]
name = "fastgpt"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 23000
3.2、客户端启动
# 在当前目录下启动cmd程序,执行下面
frpc.exe -c frpc.toml

tcp:
image.png
这里启动成功,访问到内网主机的3000端口

http:(不过不用域名就跟tcp一样了)
image.png

frp + nginx 配置多人共用的http 内网穿透服务

frp + nginx 配置多人共用的http 内网穿透服务

原文链接:https://juejin.im/post/5c49313f6fb9a049eb3c488f

frp + Nginx 配置多人共用的http 内网穿透服务

 

一、 前言

frp 是一个用Go语言开发的,可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 、 http 和 https。可将一个部署在本机的web服务映射到外网。

本文主要讲如何基于frp + Nginx 配置http 内网穿透服务,承载多人同时使用,从而支持微信公众号,微信小程序的本地开发调试

所需资源:

  • 一台公网服务器或者VPS(本人用的 腾讯云的主机)
  • 一个指向到此台公网服务器的域名(本文以msh.com 为例)

本文涉及的环境

  • centos7.2
  • Nginx 1.10.1
  • frp 0.22.0
  • go 1.11.4
  • Windows 10

 

二 、 frp 原理

(请仔细阅读原理,在不理解原理的情况下上手配置容易出错,且很难定位原因。这都是本人所经历的惨痛教训)

以本人搭建的frp内网穿透服务为例:

 

第一步: 配置无误的情况下,frp服务端frp客户端先后启动,建立通信隧道,其中:

  • frp服务端监听http 7071端口(此端口可自定义),接收此端口下所有外网用户请求
  • frp客户端代理本地想要暴露给外网的web服务端口,本文以8585 , 8686 端口为例

 

第二步: 通过配置Nginx反向代理,将指向本台公网服务器的dev.msh.com 下的子域名,映射到服务器的7071端口,也就是frp监听的那个端口。 外网用户访问dev.msh.com下的子域名,例如 :

  • a.dev.msh.com
  • b.dev.msh.com

等同于访问msh.com:7071,会 触发 frp服务端和客户端的互动,从而http请求由frp服务端传递到frp客户端

 

第三步: frp客户端收到http请求后,基于自定义配置,则做如下处理:

  • 监听到http请求中的域名为 a.dev.msh.com,则将请求转发到我本地的8585web服务端口
  • 监听到http请求中的域名为 b.dev.msh.com,则将请求转发到我本地的8686web服务端口

 

第四步: 本地的web服务收到http请求后,对请求做处理,并完成响应

 

第五步: frp客户端将响应结果回传给frp的服务端。服务端最终将响应回传给外网用户

 

第六步: 最终的实测效果为:

  • 访问 a.dev.msh.com,等同于访问我本地的localhost:8585
  • 访问 b.dev.msh.com,等同于访问我本地的localhost:8686

 

三 、 准备工作

 

3.1 在域名解析后台配置子域名

本文以msh.com 为例:

登录域名的解析后台,在msh.com下增加两条A记录: dev , *.dev,记录值为部署frp服务端的公网服务器的ip。

代表dev.msh.com下的所有的子域名,会全部指向此台公网服务器。

 

3.2 关于 go语言环境

因为本文采用的是绿色安装,所以不需要配置go语言环境。多谢 Tylerrrkd 指正

 

四、服务端配置

 

4.1 frp服务端安装配置

下载解压

# 下载
wget https://github.com/fatedier/frp/releases/download/v0.22.0/frp_0.22.0_linux_amd64.tar.gz
# 解压
 tar -zxvf frp_0.22.0_linux_amd64.tar.gz 
 
复制代码

修改配置文件

解压后进入解压目录,找到frps.ini文件, 做如下配置 。配置说明请参见各项对应的注释

[common]
# frp监听的端口,用作服务端和客户端通信
bind_port = 7000

# 服务端通过此端口接监听和接收公网用户的http请求
vhost_http_port = 7071

# frp提供了一个控制台,可以通过这个端口访问到控制台。可查看frp当前有多少代理连接以及对应的状态
dashboard_port = 7500


# 服务端的subdomain_host需要和客户端配置文件中的subdomain、local_port配合使用,
# 可通过{subdomain}.{subdomain_host} 的域名格式来访问自己本地的 web 服务。
# 假如服务端的subdomain_host为dev.msh.com,客户端某个配置组中的
# subdomain为a,local_port为8585,
# 则:
# 访问 a.dev.msh.com ,等同于访问本地的localhost:8585

subdomain_host = dev.msh.com

复制代码

启动frp服务端

./frps -c frps.ini
复制代码

 

4.2 Nginx反向代理配置

(关于如何安装Nginx就不过多赘述,详细安装教程请参考 Nginx Linux详细安装部署教程)

修改 Nginx.conf 文件

	# frp的接收http请求的反向代理
	server {
		listen 80;
		server_name *.dev.msh.com  dev.msh.com;
		
		location / {
			# 7071端口即为frp监听的http端口
			proxy_pass http://127.0.0.1:7071; 
			proxy_set_header Host $host:80;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			
			proxy_set_header Upgrade $http_upgrade;
			proxy_set_header Connection "upgrade";
			
			proxy_connect_timeout 7d;
			proxy_send_timeout 7d;
			proxy_read_timeout 7d;

			}
		# 防止爬虫抓取
		if ($http_user_agent ~* "360Spider|JikeSpider|Spider|spider|bot|Bot|2345Explorer|curl|wget|webZIP|qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot|NSPlayer|bingbot")
			{
				return 403;
			}
	}
复制代码

让Nginx重新加载配置文件

/usr/local/Nginx/sbin/Nginx   -s reload
复制代码

 

4.3 开启防火墙端口

# 开启防火墙端口   7000端口和7071端口即为上面配置的bind_port和vhost_http_port端口
firewall-cmd --zone=public --add-port=7000/tcp --permanent
firewall-cmd --zone=public --add-port=7071/tcp --permanent

# 开启后重启防火墙,使得刚刚的修改生效
firewall-cmd --reload
复制代码

 

五、 客户端安装配置

下载客户端

去github上面下载最新版的 windows客户端 github.com/fatedier/fr… ,找到 frp_0.23.1_windows_amd64.zip,点击下载即可

(Mac用户请下载Mac版本的客户端)

解压后,编辑 frpc.ini 文件

[common]
# 部署frp服务端的公网服务器的ip
server_addr = 132.232.64.79
# 和服务端的bind_port保持一致
server_port = 7000


# 代理服务一 ,[]内的代理服务名称在全局范围内确保唯一,每个人的每个代理服务不能重名,
# 否则会影响正常使用。
 [http-a]
type = http
# local_port代表你想要暴露给外网的本地web服务端口
local_port = 8585
# subdomain 在全局范围内要确保唯一,每个代理服务的subdomain不能重名,否则会影响正常使用。
# 客户端的subdomain需和服务端的subdomain_host配合使用
subdomain = a


# 代理服务二  ,各项配置说明请参考配置组一
[http-b]
type = http
local_port = 8686
subdomain = b
 
           
复制代码

启动 客户端

在frp解压目录下右键打开 powershell 或者cmd,执行如下命令

 ./frpc.exe -c .\frpc.ini
复制代码

如果窗口提示 『start proxy success』 ,则代表frp服务端和frp客户端的通信隧道建立成功

测试访问

在浏览器里面访问 http://a.dev.msh.com、测试本地的web服务是否已经暴露给外网

 

六、问题解疑

 

疑问: 为什么要搭建自己的内网穿透服务,而不是购买类似花生壳这种收费的内网穿透服务?

解疑: 花生壳收费过高,一个旗舰版的一年都需要868元,而且只有4个端口映射,意味着只有4名开发人员同时用都可能不够。一个中型的互联网公司都有四五十人或者上百人,如果用花生壳的话,每年都需要三四万花费,显然不是个小数目。而对于一家互联网公司,都是有自己的服务器资源和域名资源的,既然如此,何不搭建一个自有的内网穿透服务?

 

疑问:微信小程序只支持https协议,而刚搭建的是http内网穿透,不适用怎么办?

解疑: 可以在微信Web开发者工具里面找到项目设置,把 『不校验合法域名、业务域名、TLS版本以及HTTPS证书』 项勾选即可。这样就可以在生产环境下走https协议,本地开发环境下走http协议

关于生产环境下怎么部署https,请参考本人在掘金上的这篇文章全站HTTPS升级系列

另外关于本地开发环境下怎么部署https,曾经尝试过mkcert、jdk的keystore ,然而最终没有找打一个切实可行的方案

 

疑问:我搭建的内网穿透服务,怎么限定只有内部成员可用,防止外人随意『搭便车』?

解疑: 可以基于token参数来完成身份验证。服务端和客户端的 common 配置中的 token 参数一致则身份验证通过。

frp 和 nginx 搭建一个内网穿透服务器

frp 和 nginx 搭建一个内网穿透服务器

相关资料

  • frp下载 :https://github.com/fatedier/frp
  • 相关文档: https://github.com/fatedier/frp

下载

下载地址:https://github.com/fatedier/f...
选择对应的版本进行下载

wget https://github.com/fatedier/frp/releases/download/v0.21.0/frp_0.21.0_linux_386.tar.gz

如果是windows需要下载windos版本

wget https://github.com/fatedier/frp/releases/download/v0.21.0/frp_0.21.0_darwin_amd64.tar.gz

下载后、我的服务端是centos 客户端是windows

服务端需要关注的文件是 frps、frps.ini
客户端需要关注的文件是 frpc(或者是frpc.exe)、frpc.ini

注意,如果运行的环境是windows就要运行windows版本的,也就是exe后缀的

配置服务端

配置文件

# frps.ini
[common]
#服务端需要开启的端口(与客户端绑定的进行通信的端口)
bind_port = 7000
#服务端需要开启的端口(访问客户端web服务自定义的端口号)
vhost_http_port = 8081
auth_token = websong

type = http
custom_domains = abc.baidu.com
auth_token = websong

配置文件说明

  • bind_port
    服务端需要开启的端口
  • vhost_http_port
    服务端需要开启的端口
  • auth_token
    需要客户端的auth_token与此一样
  • type
    其实除了http还有其他参数,比如tcp,这里只讲述http,其他的请看相关文档: https://github.com/fatedier/frp
  • custom_domains
    域名
    具体接下来在配置客户端说明

启动服务

正常启动,ctrl+c能推出

 ./frps -c ./frps.ini

后台启动

nohup ./frps -c ./frps.ini &

如果有兴趣,更可以设置成开机启动(这里不讲述)

配置客户端

配置文件

# frpc.ini
[common]
server_addr = 48.104.176.184
server_port = 7000
auth_token = websong

[web6]
type = http
local_port = 80
custom_domains =b.abc.baidu.com

配置文件讲解

  • server_addr
    对应服务器ip ,
  • server_port
    与服务端配置bind_port一样
  • auth_token
    与服务端配置auth_token一样
  • [web6]
    这个是唯一的,假如在另外一个客户端用了web6将会报明显的错误
  • local_port
    此端口,假如是80,那就是访问客户端机器的80端口
  • custom_domains
    域名 这里重点说一下,这个参数可以填的域名有

     abc.baidu.com
    *.abc.baidu.com

    但是,这些域名都是需要解析到服务器ip的
    *.abc.baidu.com 这里就需要使用到域名泛解析
    具体百度即可

客户端启动

./frpc -c ./frpc.ini

windows

 ./frpc.exe -c ./frpc.ini

后台启动前面加 nohup 跟服务端一样

nohup ./frpc.exe -c ./frpc.ini

穿透成功

启动网站

如果以上服务端启动,客户端启动都没问题的话
以客户端的配置的域名:custom_domains
和 服务端配置的端口vhost_http_port在浏览器打开即可
也就是 b.abc.baidu.com:8081
其实这些就相当于访问你客户端本机的
127.0.0.1:80 或者localhost:80,
这个80端口是客户端配置文件的的local_port

至此内网穿透完成

但是

  • 但是刚刚有没有发现,访问的是带端口的网址,b.abc.baidu.com:8081
  • 如果不想带端口呢,浏览器的默认端口是80,也就是说,我把服务端vhost_http_port,配置成80就好了,当然这样能解决
  • 但是如果服务器有其他程序占用80端口呢,比如nginx,总不能把nginx换成其他端口吧,那我的博客www.blog.com就因为这个得改成www.blog.com:9090 假设改成9090,所以肯定有办法公用的
  • 办法就是,我们可以利用nginx的反向代理就能完成,请接下来往下看 配置nginx

原文地址:http://www.taoluyuan.com/index.php/archives/42/

配置nginx

配置文件

server{
 listen 80;
    server_name *.abc.baidu.com;
    index index.php index.html index.htm default.php default.htm default.html;
    root /www/wwwroot/abc/;

 location / 
    {
        proxy_pass http://48.104.176.184:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header REMOTE-HOST $remote_addr;                       
    }
}

说明

如配置文件所属

  • 本来是需要访问 http://b.abc.baidu.com:8081 的
  • nginx使用了泛域名配置,*.abc.baidu.com (你自己的域名肯定不一样)
 server_name *.abc.baidu.com;
  • 反向代理配置
 proxy_pass http://48.104.176.184:8081;

ip是服务端的ip,端口是服务端配置vhost_http_port 8081

至于nginx的其他参数,跟平常大多数nginx配置网站参数一样
这里使用到了nginx泛域名解析,和反向代理

完成打开网站

如果客户端配置的custom_domains是b.abc.baidu.com
在浏览器输入 b.abc.baidu.com ,就不用带端口号8081 ,应为已经被nginx反向代理了;
这样做的好处是

  • 使用的时候直接在浏览器输入域名就可以,不用输入端口,用户也不用知道服务端的vhost_http_port 端口是什么,
  • 让服务器其他网站的端口可以不用改;

原文地址:http://www.taoluyuan.com/index.php/archives/42/

今天关于Nodejs实现内网穿透服务nodejs 内网穿透的介绍到此结束,谢谢您的阅读,有关Angrok 一个内网穿透服务、docker云部署frp实现内网穿透,访问内网主机、frp + nginx 配置多人共用的http 内网穿透服务、frp 和 nginx 搭建一个内网穿透服务器等更多相关知识的信息可以在本站进行查询。

本文标签: