From 94db94ce1b9a13382d22b9a8696ba16d5a2fa83c Mon Sep 17 00:00:00 2001 From: zhengbaoyang Date: Wed, 15 Jun 2016 14:39:01 +0800 Subject: [PATCH] first --- clcolor.go | 84 +++++++++++++ gomitmproxy.go | 330 +++++++++++++++++++++++++++++++++++++++++++++++++ proxy.png | Bin 0 -> 14890 bytes 3 files changed, 414 insertions(+) create mode 100644 clcolor.go create mode 100644 gomitmproxy.go create mode 100644 proxy.png diff --git a/clcolor.go b/clcolor.go new file mode 100644 index 0000000..5a6672c --- /dev/null +++ b/clcolor.go @@ -0,0 +1,84 @@ +package main + +import ( + "fmt" + "runtime" +) + +const ( + TextBlack = iota + 30 + TextRed + TextGreen + TextYellow + TextBlue + TextMagenta + TextCyan + TextWhite +) + +func Black(str string) string { + return textColor(TextBlack, str) +} + +func Red(str string) string { + return textColor(TextRed, str) +} + +func Green(str string) string { + return textColor(TextGreen, str) +} + +func Yellow(str string) string { + return textColor(TextYellow, str) +} + +func Blue(str string) string { + return textColor(TextBlue, str) +} + +func Magenta(str string) string { + return textColor(TextMagenta, str) +} + +func Cyan(str string) string { + return textColor(TextCyan, str) +} + +func White(str string) string { + return textColor(TextWhite, str) +} + +func textColor(color int, str string) string { + if IsWindows() { + return str + } + + switch color { + case TextBlack: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextBlack, str) + case TextRed: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextRed, str) + case TextGreen: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextGreen, str) + case TextYellow: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextYellow, str) + case TextBlue: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextBlue, str) + case TextMagenta: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextMagenta, str) + case TextCyan: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextCyan, str) + case TextWhite: + return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextWhite, str) + default: + return str + } +} + +func IsWindows() bool { + if runtime.GOOS == "windows" { + return true + } else { + return false + } +} diff --git a/gomitmproxy.go b/gomitmproxy.go new file mode 100644 index 0000000..5af60e7 --- /dev/null +++ b/gomitmproxy.go @@ -0,0 +1,330 @@ +/* +Author:shepbao +Time:2016-06-01 +*/ + +package main + +import ( + "bufio" + "bytes" + "compress/flate" + "compress/gzip" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "math" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "regexp" + "strconv" + "time" + + "log" +) + +const ( + MaxOutstanding = 10 +) + +var Version string = "1.0" +var sem = make(chan int, MaxOutstanding) + +type AccessStatistics struct { + AllCount int64 + HostCount map[string]int64 +} + +var transport = &http.Transport{ + ResponseHeaderTimeout: 30 * time.Second, +} + +//config json +type Config struct { + Port string `json :"port"` //"8080" + Raddr string `json:"raddr,omitempty"` //"localhost:8888" +} + +var port = flag.String("port", "8080", "Listen port") +var raddr = flag.String("raddr", "", "Remote addr") +var monitor = flag.Bool("m", false, "monitor mode") + +var cfg = flag.String("conf", "./config", "config file") +var cf *Config + +var logFile *os.File +var logger *log.Logger + +func main() { + var err error + logFile, err = os.Create("error.log") + if err != nil { + log.Fatalln("fail to create log file!") + } + + logger = log.New(logFile, "[gomitmproxy]", log.LstdFlags|log.Llongfile) + flag.Parse() + + cf, err = ParseConfig(*cfg) + if err != nil { + logger.Println("ParseConfig err:", err) + } else { + if len(cf.Port) == 0 { + log.Fatal("Miss Port") + } + *port = cf.Port + + if len(cf.Raddr) == 0 { + log.Fatal("Miss Raddr") + } + *raddr = cf.Raddr + } + + log.Println("Listen port: ", *port) + if len(*raddr) != 0 { + log.Println("Connect to Raddr: ", *raddr) + } + + ln, err := net.Listen("tcp", ":"+*port) + if err != nil { + logger.Println("listen err:", err) + } + defer ln.Close() + + for { + conn, err := ln.Accept() + if err != nil { + logger.Println("Accept err:", err) + continue + } + go handleConn(conn) + + } +} + +//处理连接 +func handleConn(conn net.Conn) { + reader := bufio.NewReader(conn) + req, err := http.ReadRequest(reader) + if err != nil { + logger.Println("readRequest err:", err) + conn.Close() + return + } + + host := req.Host + matched, _ := regexp.MatchString(":[0-9]+$", host) + if !matched { + host += ":80" + } + conn_proxy, err := Connect(host) + if err != nil { + return + } + defer conn_proxy.Close() + + if req.Method == "CONNECT" { + b := []byte("HTTP/1.1 200 Connection Established\r\n" + + "Proxy-Agent: golang_proxy/" + Version + "\r\n\r\n") + _, err := conn.Write(b) + if err != nil { + logger.Println("Write Connect err:", err) + return + } + } else { + req.Header.Del("Proxy-Connection") + req.Header.Set("Connection", "Keep-Alive") + if err = req.Write(conn_proxy); err != nil { + logger.Println("send to server err", err) + return + } + } + + if *monitor && req.Method != "CONNECT" { + + resp, err := http.ReadResponse(bufio.NewReader(conn_proxy), req) + if err != nil { + logger.Println("read response err:", err) + return + } + + respDump, err := httputil.DumpResponse(resp, true) + if err != nil { + logger.Println("respDump err:", err) + } + + _, err = conn.Write(respDump) + if err != nil { + logger.Println("conn write err:", err) + } + // reqDump, _ := httputil.DumpRequest(req, false) + // log.Println(string(reqDump), string(respDump)) + go httpDump(req, resp) + + } else { + err = Transport(conn, conn_proxy) + if err != nil { + logger.Println("Transport err:", err) + } + } +} + +//连接真实的主机 +func Connect(host string) (net.Conn, error) { + if len(*raddr) == 0 { + conn, err := net.DialTimeout("tcp", host, time.Second*30) + if err != nil { + // logger.Println("connect", host, "err", err) + return nil, err + } + return conn, nil + } + + conn_proxy, err := net.DialTimeout("tcp", *raddr, time.Second*30) + if err != nil { + logger.Println("connect proxy err", err) + return nil, err + } + err = connectProxyServer(conn_proxy, host) + if err != nil { + logger.Println("connectServer err:", err) + return nil, err + } + return conn_proxy, nil +} + +//连接代理服务器 +func connectProxyServer(conn net.Conn, addr string) error { + + req := &http.Request{ + Method: "CONNECT", + URL: &url.URL{Host: addr}, + Host: addr, + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + } + req.Header.Set("Proxy-Connection", "keep-alive") + + if err := req.Write(conn); err != nil { + return err + } + + resp, err := http.ReadResponse(bufio.NewReader(conn), req) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + return nil +} + +//两个io口的连接 +func Transport(conn1, conn2 net.Conn) (err error) { + rChan := make(chan error, 1) + wChan := make(chan error, 1) + + go MyCopy(conn1, conn2, wChan) + go MyCopy(conn2, conn1, rChan) + + select { + case err = <-wChan: + case err = <-rChan: + } + + return +} + +func MyCopy(src io.Reader, dst io.Writer, ch chan<- error) { + _, err := io.Copy(dst, src) + ch <- err +} + +//配置文件解析 +func ParseConfig(cfg string) (*Config, error) { + var conf Config + configContext, err := ioutil.ReadFile(cfg) + if err != nil { + return nil, err + } + err = json.Unmarshal(configContext, &conf) + if err != nil { + return nil, err + } + return &conf, nil +} + +//复制http头 +func copyHeader(src, dst http.Header) { + for k, vv := range src { + for _, v := range vv { + dst.Add(k, v) + } + } +} + +//打印http请求和响应 +func httpDump(req *http.Request, resp *http.Response) { + defer resp.Body.Close() + var respStatusStr string + respStatus := resp.StatusCode + respStatusHeader := int(math.Floor(float64(respStatus / 100))) + switch respStatusHeader { + case 2: + respStatusStr = Green("<--" + strconv.Itoa(respStatus)) + case 3: + respStatusStr = Yellow("<--" + strconv.Itoa(respStatus)) + case 4: + respStatusStr = Magenta("<--" + strconv.Itoa(respStatus)) + case 5: + respStatusStr = Red("<--" + strconv.Itoa(respStatus)) + } + fmt.Println(Green("Request:")) + fmt.Printf("%s %s %s\n", Blue(req.Method), req.RequestURI, respStatusStr) + for headerName, headerContext := range req.Header { + fmt.Printf("%s: %s\n", Blue(headerName), headerContext) + } + fmt.Println(Green("Response:")) + for headerName, headerContext := range resp.Header { + fmt.Printf("%s: %s\n", Blue(headerName), headerContext) + } + + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + logger.Println("read resp body err:", err) + } else { + acceptEncode := resp.Header["Content-Encoding"] + var respBodyBin bytes.Buffer + w := bufio.NewWriter(&respBodyBin) + w.Write(respBody) + w.Flush() + for _, compress := range acceptEncode { + switch compress { + case "gzip": + r, err := gzip.NewReader(&respBodyBin) + if err != nil { + logger.Println("gzip reader err:", err) + } else { + defer r.Close() + respBody, _ = ioutil.ReadAll(r) + } + break + case "deflate": + r := flate.NewReader(&respBodyBin) + defer r.Close() + respBody, _ = ioutil.ReadAll(r) + break + } + } + fmt.Printf("%s\n", string(respBody)) + } + + fmt.Printf("%s%s%s\n", Black("####################"), Cyan("END"), Black("####################")) +} diff --git a/proxy.png b/proxy.png new file mode 100644 index 0000000000000000000000000000000000000000..179d0a82901a938e1aa02cdd6893a25bce167ded GIT binary patch literal 14890 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