From ec7d181d5a9531c2a1c2401701350b346f23cb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B2=B3?= Date: Sun, 4 Nov 2018 23:19:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 214 +++++----------------------------------------------- README.md | 149 ++++++------------------------------ client.go | 147 ++++++++++++++++++++++++++++++++++++ config.json | 20 +++++ http.go | 99 ++++++++++++++++++++++++ json.go | 50 ++++++++++++ main.go | 49 ++++++++++++ server.go | 193 ++++++++++++++++++++++++++++++++++++++++++++++ verify.go | 12 +++ 9 files changed, 608 insertions(+), 325 deletions(-) create mode 100755 client.go create mode 100755 config.json create mode 100755 http.go create mode 100755 json.go create mode 100755 main.go create mode 100755 server.go create mode 100755 verify.go diff --git a/LICENSE b/LICENSE index 261eeb9..b442934 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +MIT License - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +Copyright (c) 2018 - 1. Definitions. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 638aa7a..1ffa0b2 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,29 @@ -# easyProxy -轻量级、较高性能http代理服务器,主要应用与内网穿透。支持多站点配置、客户端与服务端连接中断自动重连,多路传输,大大的提高请求处理速度,go语言编写,无第三方依赖,经过测试内存占用小,普通场景下,仅占用10m内存。 +# rproxy +简单的反向代理用于内网穿透 -## 背景 -我有一个小程序的需求,但是小程序的数据源必须从内网才能抓取到,但是又苦于内网服务器没有公网ip,所以只能内网穿透了。 +**特别注意,此工具只适合小文件类的访问测试,用来做做数据调试。当初也只是用于微信公众号开发,所以定位也是如此** -用了一段时间ngrok做内网穿透,可能由于功能比较强大,配置起来挺麻烦的,加之开源版有内存的泄漏,很是闹心。 +## 前言 +最近周末闲来无事,想起了做下微信公共号的开发,但微信限制只能80端口的,自己用的城中村的那种宽带,共用一个公网,没办法自己用路由做端口映射。自己的服务器在腾讯云上,每次都要编译完后用ftp上传再进行调试,非常的浪费时间。 一时间又不知道上哪找一个符合我的这种要求的工具,就索性自己构思了下,整个工作流程大致为: -正好最近在看go相关的东西,所以做了一款代理服务器,功能比较简单,用于内网穿透最为合适。 - -## 特点 - -- [x] 支持多站点配置 -- [x] 断线自动重连 -- [x] 支持多路传输,提高并发 -## 安装 -1. release安装 -> https://github.com/cnlh/easyProxy/releases - -下载对应的系统版本即可(目前linux和windows只编译了64位的),服务端和客户端共用一个程序,go语言开发,无需任何第三方依赖 - -2. 源码安装 -- 安装源码 -> go get github.com/cnlh/easyProxy -- 编译(无第三方模块) -> go build - -## 使用 -- 服务端 - -``` -./rproxy -mode server -vkey DKibZF5TXvic1g3kY -tcpport=8284 -httpport=8024 -``` - -名称 | 含义 ----|--- -mode | 运行模式(client、server不写默认client) -vkey | 验证密钥 -tcpport | 服务端与客户端通信端口 -httpport | 代理的http端口(与nginx配合使用) - -- 客户端 - - -``` -建立配置文件 config.json -``` - - -``` -./rproxy -config config.json -``` - -- 详细说明 - -https://github.com/cnlh/easyProxy/wiki/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B - 名称 | 含义 ----|--- -config | 配置文件路径 - -## 配置文件config.json - -``` -{ - "Server": { - "ip": "123.206.77.88", - "tcp": 8224, - "vkey": "DKibZF5TXvic1g3kY", - "num": 10 - }, - "SiteList": [ - { - "host": "a.server.ourcauc.com", - "url": "10.1.50.203", - "port": 80 - }, - { - "host": "b.server.ourcauc.com", - "url": "10.1.50.196", - "port": 4000 - } - ] -} -``` - 名称 | 含义 ----|--- -ip | 服务端ip地址 -tcp | 服务端与客户端通信端口 -vkey | 验证密钥 -num | 服务端与客户端通信连接数 -SiteList | 本地解析的域名列表 -host | 域名地址 -url | 内网代理的地址 -port | 内网代理的地址对应的端口 - -## 运行流程解析 - - - -``` -graph TD -A[通过域名访问对应内网服务]-->B[nginx代理转发该域名服务端监听的8024端口] -B-->C[服务端将请求发送到客户端上] -C-->D[客户端收到请求信息,根据host判断对应的内网的请求地址,执行对应请求] -D-->E[将请求结果返回给服务端] -E-->F[服务端收到后返回给访问者] -``` - -## nginx代理配置示例 -``` -upstream nodejs { - server 127.0.0.1:8024; - keepalive 64; -} -server { - listen 80; - server_name *.server.ourcauc.com; - location / { - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host:8224; - proxy_set_header X-Nginx-Proxy true; - proxy_set_header Connection ""; - proxy_pass http://nodejs; - } -} -``` -## 域名配置示例 -> -server A 123.206.77.88 - -> *.server CNAME server.ourcauc.com. +## 工作原理 +> 外部请求自己服务器上的HTTP服务端 -> 将数据传递给Socket服务器 -> Socket服务器将数据发送至已连接的Socket客户端 -> Socket客户端收到数据 -> 使用http请求本地http服务端 -> 本地http服务端处理相关后返回 -> Socket客户端将返回的数据发送至Socket服务端 -> Socket服务端解析出数据后原路返回至外部请求的HTTP +## 使用方法 +> 1、go get github.com/ying32/rproxy +> 2、go build +> 3、服务端运行runsvr.bat或者runsvr.sh +> 4、客户端运行runcli.bat或者runcli.sh + +## 命令行说明 +> --tcpport Socket连接或者监听的端口 +> --httpport 当mode为server时为服务端监听端口,当为mode为client时为转发至本地客户端的端口 +> --mode 启动模式,可选为client、server,默认为client +> --svraddr 当mode为client时有效,为连接服务器的地址,不需要填写端口 +> --vkey 客户端与服务端建立连接时校验的加密key,简单的。 ## 操作系统支持 支持Windows、Linux、MacOSX等,无第三方依赖库。 +## 二进制下载 +https://github.com/ying32/rproxy/releases/tag/v0.4 diff --git a/client.go b/client.go new file mode 100755 index 0000000..f2fd99a --- /dev/null +++ b/client.go @@ -0,0 +1,147 @@ +package main + +import ( + "encoding/binary" + "errors" + "log" + "net" + "net/http" + "strings" + "sync" + "time" +) + +var ( + disabledRedirect = errors.New("disabled redirect.") +) + +type TRPClient struct { + svrAddr string + tcpNum int + sync.Mutex +} + +func NewRPClient(svraddr string, tcpNum int) *TRPClient { + c := new(TRPClient) + c.svrAddr = svraddr + c.tcpNum = tcpNum + return c +} + +func (c *TRPClient) Start() error { + for i := 0; i < c.tcpNum; i++ { + go c.newConn() + } + for { + time.Sleep(5 * time.Second) + } + return nil +} + +func (c *TRPClient) newConn() error { + c.Lock() + conn, err := net.Dial("tcp", c.svrAddr) + if err != nil { + log.Println("连接服务端失败,五秒后将重连") + time.Sleep(time.Second * 5) + c.Unlock() + c.newConn() + return err + } + c.Unlock() + conn.(*net.TCPConn).SetKeepAlive(true) + conn.(*net.TCPConn).SetKeepAlivePeriod(time.Duration(2 * time.Second)) + return c.process(conn) +} + +func (c *TRPClient) werror(conn net.Conn) { + conn.Write([]byte("msg0")) +} + +func (c *TRPClient) process(conn net.Conn) error { + if _, err := conn.Write(getverifyval()); err != nil { + return err + } + val := make([]byte, 4) + for { + _, err := conn.Read(val) + if err != nil { + log.Println("服务端断开,五秒后将重连", err) + time.Sleep(5 * time.Second) + go c.newConn() + return err + } + flags := string(val) + switch flags { + case "vkey": + log.Fatal("vkey不正确,请检查配置文件") + case "sign": + _, err := conn.Read(val) + nlen := binary.LittleEndian.Uint32(val) + log.Println("收到服务端数据,长度:", nlen) + if nlen <= 0 { + log.Println("数据长度错误。") + c.werror(conn) + continue + } + raw := make([]byte, nlen) + n, err := conn.Read(raw) + if err != nil { + return err + } + if n != int(nlen) { + log.Printf("读取服务端数据长度错误,已经读取%dbyte,总长度%d字节\n", n, nlen) + c.werror(conn) + continue + } + req, err := DecodeRequest(raw) + if err != nil { + log.Println("DecodeRequest错误:", err) + c.werror(conn) + continue + } + rawQuery := "" + if req.URL.RawQuery != "" { + rawQuery = "?" + req.URL.RawQuery + } + log.Println(req.URL.Path + rawQuery) + client := new(http.Client) + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return disabledRedirect + } + resp, err := client.Do(req) + disRedirect := err != nil && strings.Contains(err.Error(), disabledRedirect.Error()) + if err != nil && !disRedirect { + log.Println("请求本地客户端错误:", err) + c.werror(conn) + continue + } + if !disRedirect { + defer resp.Body.Close() + } else { + resp.Body = nil + resp.ContentLength = 0 + } + respBytes, err := EncodeResponse(resp) + if err != nil { + log.Println("EncodeResponse错误:", err) + c.werror(conn) + continue + } + n, err = conn.Write(respBytes) + if err != nil { + log.Println("发送数据错误,错误:", err) + } + if n != len(respBytes) { + log.Printf("发送数据长度错误,已经发送:%dbyte,总字节长:%dbyte\n", n, len(respBytes)) + } else { + log.Printf("本次请求成功完成,共发送:%dbyte\n", n) + } + case "msg0": + log.Println("服务端返回错误。") + default: + log.Println("无法解析该错误。") + } + } + return nil +} diff --git a/config.json b/config.json new file mode 100755 index 0000000..c405931 --- /dev/null +++ b/config.json @@ -0,0 +1,20 @@ +{ + "Server": { + "ip": "123.206.77.88", + "tcp": 8224, + "vkey": "DKibZF5TXvic1g3kY", + "num": 10 + }, + "SiteList": [ + { + "host": "a.server.ourcauc.com", + "url": "10.1.50.203", + "port": 80 + }, + { + "host": "b.server.ourcauc.com", + "url": "10.1.50.196", + "port": 4000 + } + ] +} diff --git a/http.go b/http.go new file mode 100755 index 0000000..d05448b --- /dev/null +++ b/http.go @@ -0,0 +1,99 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "strings" +) + +/* + + http.ReadRequest() + http.ReadResponse() + httputil.DumpRequest() + httputil.DumpResponse() +*/ + +// 将request 的处理 +func EncodeRequest(r *http.Request) ([]byte, error) { + raw := bytes.NewBuffer([]byte{}) + // 写签名 + binary.Write(raw, binary.LittleEndian, []byte("sign")) + reqBytes, err := httputil.DumpRequest(r, true) + if err != nil { + return nil, err + } + // 写body数据长度 + 1 + binary.Write(raw, binary.LittleEndian, int32(len(reqBytes)+1)) + // 判断是否为http或者https的标识1字节 + binary.Write(raw, binary.LittleEndian, bool(r.URL.Scheme == "https")) + if err := binary.Write(raw, binary.LittleEndian, reqBytes); err != nil { + return nil, err + } + return raw.Bytes(), nil +} + +// 将字节转为request +func DecodeRequest(data []byte) (*http.Request, error) { + if len(data) <= 100 { + return nil, errors.New("待解码的字节长度太小") + } + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(data[1:]))) + if err != nil { + return nil, err + } + str := strings.Split(req.Host, ":") + req.Host, err = getHost(str[0]) + if err != nil { + return nil, err + } + scheme := "http" + if data[0] == 1 { + scheme = "https" + } + req.URL, _ = url.Parse(fmt.Sprintf("%s://%s%s", scheme, req.Host, req.RequestURI)) + req.RequestURI = "" + + return req, nil +} + +//// 将response转为字节 +func EncodeResponse(r *http.Response) ([]byte, error) { + raw := bytes.NewBuffer([]byte{}) + binary.Write(raw, binary.LittleEndian, []byte("sign")) + respBytes, err := httputil.DumpResponse(r, true) + if err != nil { + return nil, err + } + binary.Write(raw, binary.LittleEndian, int32(len(respBytes))) + if err := binary.Write(raw, binary.LittleEndian, respBytes); err != nil { + return nil, err + } + return raw.Bytes(), nil +} + +//// 将字节转为response +func DecodeResponse(data []byte) (*http.Response, error) { + + resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(data)), nil) + if err != nil { + return nil, err + } + return resp, nil +} + +func getHost(str string) (string, error) { + for _, v := range config.SiteList { + if v.Host == str { + return v.Url + ":" + strconv.Itoa(v.Port), nil + } + } + return "", errors.New("没有找到解析的的host!") +} diff --git a/json.go b/json.go new file mode 100755 index 0000000..10269b2 --- /dev/null +++ b/json.go @@ -0,0 +1,50 @@ +package main + +import ( + "encoding/json" + "errors" + "io/ioutil" +) + +//定义配置文件解析后的结构 +type Server struct { + Ip string + Port int + Tcp int + Vkey string + Num int +} + +type Site struct { + Host string + Url string + Port int +} +type Config struct { + Server Server + SiteList []Site +} +type JsonStruct struct { +} + +func NewJsonStruct() *JsonStruct { + return &JsonStruct{} +} +func (jst *JsonStruct) Load(filename string) (Config, error) { + data, err := ioutil.ReadFile(filename) + config := Config{} + if err != nil { + return config, errors.New("配置文件打开错误") + } + err = json.Unmarshal(data, &config) + if err != nil { + return config, errors.New("配置文件解析错误") + } + if config.Server.Tcp <= 0 || config.Server.Tcp >= 65536 { + return config, errors.New("请输入正确的tcp端口") + } + if config.Server.Vkey == "" { + return config, errors.New("密钥不能为空!") + } + return config, nil +} diff --git a/main.go b/main.go new file mode 100755 index 0000000..b761a27 --- /dev/null +++ b/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "flag" + "fmt" + "log" +) + +var ( + configPath = flag.String("config", "config.json", "配置文件路径") + tcpPort = flag.Int("tcpport", 8284, "Socket连接或者监听的端口") + httpPort = flag.Int("httpport", 8024, "当mode为server时为服务端监听端口,当为mode为client时为转发至本地客户端的端口") + rpMode = flag.String("mode", "client", "启动模式,可选为client、server") + verifyKey = flag.String("vkey", "", "验证密钥") + config Config + err error +) + +func main() { + flag.Parse() + log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) + if *rpMode == "client" { + JsonParse := NewJsonStruct() + config, err = JsonParse.Load(*configPath) + if err != nil { + log.Fatalln(err) + } + *verifyKey = config.Server.Vkey + log.Println("客户端启动,连接:", config.Server.Ip, ", 端口:", config.Server.Tcp) + cli := NewRPClient(fmt.Sprintf("%s:%d", config.Server.Ip, config.Server.Tcp), config.Server.Num) + cli.Start() + } else if *rpMode == "server" { + if *verifyKey == "" { + log.Fatalln("必须输入一个验证的key") + } + if *tcpPort <= 0 || *tcpPort >= 65536 { + log.Fatalln("请输入正确的tcp端口。") + } + if *httpPort <= 0 || *httpPort >= 65536 { + log.Fatalln("请输入正确的http端口。") + } + log.Println("服务端启动,监听tcp服务端端口:", *tcpPort, ", http服务端端口:", *httpPort) + svr := NewRPServer(*tcpPort, *httpPort) + if err := svr.Start(); err != nil { + log.Fatalln(err) + } + defer svr.Close() + } +} diff --git a/server.go b/server.go new file mode 100755 index 0000000..1b91f3a --- /dev/null +++ b/server.go @@ -0,0 +1,193 @@ +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "net/http" + "sync" + "time" +) + +type TRPServer struct { + tcpPort int + httpPort int + listener *net.TCPListener + connList chan net.Conn + sync.RWMutex +} + +func NewRPServer(tcpPort, httpPort int) *TRPServer { + s := new(TRPServer) + s.tcpPort = tcpPort + s.httpPort = httpPort + s.connList = make(chan net.Conn, 1000) + return s +} + +func (s *TRPServer) Start() error { + var err error + s.listener, err = net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP("0.0.0.0"), s.tcpPort, ""}) + if err != nil { + return err + } + go s.httpserver() + return s.tcpserver() +} + +func (s *TRPServer) Close() error { + if s.listener != nil { + err := s.listener.Close() + s.listener = nil + return err + } + return errors.New("TCP实例未创建!") +} + +func (s *TRPServer) tcpserver() error { + var err error + for { + conn, err := s.listener.AcceptTCP() + if err != nil { + log.Println(err) + continue + } + go s.cliProcess(conn) + } + return err +} + +func badRequest(w http.ResponseWriter) { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) +} + +func (s *TRPServer) httpserver() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + retry: + if len(s.connList) == 0 { + badRequest(w) + return + } + conn := <-s.connList + log.Println(r.RequestURI) + err := s.write(r, conn) + if err != nil { + log.Println(err) + conn.Close() + goto retry + return + } + err = s.read(w, conn) + if err != nil { + log.Println(err) + conn.Close() + goto retry + return + } + s.connList <- conn + conn = nil + }) + log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", s.httpPort), nil)) +} + +func (s *TRPServer) cliProcess(conn *net.TCPConn) error { + conn.SetReadDeadline(time.Now().Add(time.Duration(5) * time.Second)) + vval := make([]byte, 20) + _, err := conn.Read(vval) + if err != nil { + log.Println("客户端读超时。客户端地址为::", conn.RemoteAddr()) + conn.Close() + return err + } + if bytes.Compare(vval, getverifyval()[:]) != 0 { + log.Println("当前客户端连接校验错误,关闭此客户端:", conn.RemoteAddr()) + conn.Write([]byte("vkey")) + conn.Close() + return err + } + conn.SetReadDeadline(time.Time{}) + log.Println("连接新的客户端:", conn.RemoteAddr()) + conn.SetKeepAlive(true) + conn.SetKeepAlivePeriod(time.Duration(2 * time.Second)) + s.connList <- conn + return nil +} + +func (s *TRPServer) write(r *http.Request, conn net.Conn) error { + raw, err := EncodeRequest(r) + if err != nil { + return err + } + c, err := conn.Write(raw) + if err != nil { + return err + } + if c != len(raw) { + return errors.New("写出长度与字节长度不一致。") + } + return nil +} + +func (s *TRPServer) read(w http.ResponseWriter, conn net.Conn) (error) { + val := make([]byte, 4) + _, err := conn.Read(val) + if err != nil { + return err + } + flags := string(val) + switch flags { + case "sign": + _, err = conn.Read(val) + if err != nil { + return err + } + nlen := int(binary.LittleEndian.Uint32(val)) + if nlen == 0 { + return errors.New("读取客户端长度错误。") + } + log.Println("收到客户端数据,需要读取长度:", nlen) + raw := make([]byte, 0) + buff := make([]byte, 1024) + c := 0 + for { + clen, err := conn.Read(buff) + if err != nil && err != io.EOF { + return err + } + raw = append(raw, buff[:clen]...) + c += clen + if c >= nlen { + break + } + } + log.Println("读取完成,长度:", c, "实际raw长度:", len(raw)) + if c != nlen { + return fmt.Errorf("已读取长度错误,已读取%dbyte,需要读取%dbyte。", c, nlen) + } + resp, err := DecodeResponse(raw) + if err != nil { + return err + } + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + for k, v := range resp.Header { + for _, v2 := range v { + w.Header().Set(k, v2) + } + } + w.WriteHeader(resp.StatusCode) + w.Write(bodyBytes) + case "msg0": + return nil + default: + log.Println("无法解析此错误", string(val)) + } + return nil +} diff --git a/verify.go b/verify.go new file mode 100755 index 0000000..7caf856 --- /dev/null +++ b/verify.go @@ -0,0 +1,12 @@ +package main + +import ( + "crypto/sha1" + "time" +) + +// 简单的一个校验值 +func getverifyval() []byte { + b := sha1.Sum([]byte(time.Now().Format("2006-01-02 15") + *verifyKey)) + return b[:] +}