浏览代码

初始化

刘河 6 年之前
父节点
当前提交
ec7d181d5a
共有 9 个文件被更改,包括 612 次插入329 次删除
  1. 21 201
      LICENSE
  2. 21 128
      README.md
  3. 147 0
      client.go
  4. 20 0
      config.json
  5. 99 0
      http.go
  6. 50 0
      json.go
  7. 49 0
      main.go
  8. 193 0
      server.go
  9. 12 0
      verify.go

+ 21 - 201
LICENSE

@@ -1,201 +1,21 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "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.
+MIT License
+
+Copyright (c) 2018
+
+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:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+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.

+ 21 - 128
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  

+ 147 - 0
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
+}

+ 20 - 0
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
+    }
+  ]
+}

+ 99 - 0
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!")
+}

+ 50 - 0
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
+}

+ 49 - 0
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()
+	}
+}

+ 193 - 0
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
+}

+ 12 - 0
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[:]
+}