用Go将TCP套接字转换为HTTP的方法

101 阅读3分钟

有时我们需要与遗留的应用程序一起工作。遗留的应用程序,很难重写,也很难改变。想象一下,例如,这个应用程序正在发送原始的TCP套接字来与另一个进程进行通信。原始TCP套接字的速度很快,但它们有各种问题,例如,所有的数据都是通过网络以纯文本发送的,而且没有认证(如果我们不实现一个协议的话)。

一个解决方案是使用https连接来代替。我们也可以用一个验证承载器来验证这些请求。例如,我已经用Python和Flask创建了一个简单的http服务器。

import logging
import os
from functools import wraps

from flask import Flask, request, abort
from flask import jsonify

logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger(__name__)
app = Flask(__name__)


def authorize_bearer(bearer):
    def authorize(f):
        @wraps(f)
        def decorated_function(*args, **kws):
            if 'Authorization' not in request.headers:
                abort(401)

            data = request.headers['Authorization']

            if str.replace(str(data), 'Bearer ', '') != bearer:
                abort(401)

            return f(*args, **kws)

        return decorated_function

    return authorize


@app.route('/register', methods=['POST'])
@authorize_bearer(bearer=os.getenv('TOKEN'))
def hello_world():
    req_data = request.get_json()
    logger.info(req_data)
    return jsonify({"status": "OK", "request_data": req_data})


现在我们只需要改变我们的传统应用,使用一个http客户端而不是原始的TCP套接字。但有时这是不可能的。想象一下,例如,如果这个应用程序运行在一个没有https支持的旧操作系统上,或者我们无法在传统的应用程序中找到并编译一个http客户端。

一个可能的解决方案是隔离该应用程序,只改变TCP套接字的目标。我们可以用localhost代替原来的ip地址,并在localhost创建一个代理,监听TCP套接字并将信息发送到HTTP服务器。

我们将在Go中建立这个代理。我们可以用任何语言(Python, C#, Javascript, ...)来做。我在Go中的功夫不是很好(我更喜欢Python),但这并不难,我们可以在Windows、Linux和Mac上建立一个二进制的代理,没有任何问题。然后我们只需要把二进制文件复制到目标主机上就可以了(不需要安装,不需要SDK,什么都不需要。 只需要复制和运行)。

package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
)

func main() {
	port, closeConnection, url := parseFlags()
	openSocket(*port, *closeConnection, *url, onMessage)
}

func onMessage(url string, buffer string) {
	bearer := os.Getenv("TOKEN")
	client := &http.Client{}
	req, _ := http.NewRequest("POST", url, strings.NewReader(buffer))
	req.Header.Add("Authorization", "Bearer "+bearer)
	req.Header.Add("content-type", "application/json")
	resp, err := client.Do(req)

	if err != nil {
		log.Println(err)
	} else {
		if resp.Status == "200" {
			var result map[string]interface{}
			json.NewDecoder(resp.Body).Decode(&result)
			log.Println(result["status"])
		} else {
			log.Println("Response status: " + resp.Status)
		}
		defer resp.Body.Close()
	}
}

func parseFlags() (*string, *bool, *string) {
	port := flag.String("port", "7777", "port number")
	closeConnection := flag.Bool("close", true, "Close connection")
	url := flag.String("url", "http://localhost:5000/register", "Destination endpoint")
	flag.Parse()
	return port, closeConnection, url
}

func openSocket(port string, closeConnection bool, url string, onMessage func(url string, buffer string)) {
	PORT := "localhost:" + port
	l, err := net.Listen("tcp4", PORT)
	log.Printf("Serving %s\n", l.Addr().String())
	if err != nil {
		log.Fatalln(err)
	}
	defer l.Close()

	for {
		c, err := l.Accept()
		if err != nil {
			log.Fatalln(err)
		}
		go handleConnection(c, closeConnection, url, onMessage)
	}
}

func handleConnection(c net.Conn, closeConnection bool, url string, onMessage func(url string, buffer string)) {
	log.Printf("Accepted connection from %s\n", c.RemoteAddr().String())
	for {
		ip, port, err := net.SplitHostPort(c.RemoteAddr().String())
		netData, err := bufio.NewReader(c).ReadString('\n')
		if err != nil {
			log.Println(err)
		}

		message := map[string]interface{}{
			"body":   strings.TrimSpace(netData),
			"ipFrom": ip,
			"port":   port,
		}

		log.Printf("Making request with %s\n", message)
		bytesRepresentation, err := json.Marshal(message)
		if err != nil {
			log.Println(err)
		} else {
			//buffer := bytes.NewBuffer(bytesRepresentation)
			onMessage(url, string(bytesRepresentation))
		}

		if closeConnection {
			c.Close()
			return
		}
	}
	c.Close()

这就是全部。我们可以在几乎不改变代码的情况下升级我们的传统应用程序。

源代码可在我的github上找到