Browse Source

Merge pull request #1 from fatedier/master

First available commit
fatedier 9 years ago
parent
commit
793624c379

+ 4 - 0
.gitignore

@@ -22,3 +22,7 @@ _testmain.go
 *.exe
 *.test
 *.prof
+
+# Self
+bin/
+

+ 18 - 0
Godeps/Godeps.json

@@ -0,0 +1,18 @@
+{
+	"ImportPath": "frp",
+	"GoVersion": "go1.4",
+	"Packages": [
+		"./..."
+	],
+	"Deps": [
+		{
+			"ImportPath": "github.com/astaxie/beego/logs",
+			"Comment": "v1.5.0-9-gfb7314f",
+			"Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5"
+		},
+		{
+			"ImportPath": "github.com/vaughan0/go-ini",
+			"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
+		}
+	]
+}

+ 5 - 0
Godeps/Readme

@@ -0,0 +1,5 @@
+This directory tree is generated automatically by godep.
+
+Please do not edit.
+
+See https://github.com/tools/godep for more information.

+ 2 - 0
Godeps/_workspace/.gitignore

@@ -0,0 +1,2 @@
+/pkg
+/bin

+ 13 - 0
Godeps/_workspace/src/github.com/astaxie/beego/LICENSE

@@ -0,0 +1,13 @@
+Copyright 2014 astaxie
+
+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.

+ 63 - 0
Godeps/_workspace/src/github.com/astaxie/beego/logs/README.md

@@ -0,0 +1,63 @@
+## logs
+logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
+
+
+## How to install?
+
+	go get github.com/astaxie/beego/logs
+
+
+## What adapters are supported?
+
+As of now this logs support console, file,smtp and conn.
+
+
+## How to use it?
+
+First you must import it
+
+	import (
+		"github.com/astaxie/beego/logs"
+	)
+
+Then init a Log (example with console adapter)
+
+	log := NewLogger(10000)
+	log.SetLogger("console", "")	
+
+> the first params stand for how many channel
+
+Use it like this:	
+	
+	log.Trace("trace")
+	log.Info("info")
+	log.Warn("warning")
+	log.Debug("debug")
+	log.Critical("critical")
+
+
+## File adapter
+
+Configure file adapter like this:
+
+	log := NewLogger(10000)
+	log.SetLogger("file", `{"filename":"test.log"}`)
+
+
+## Conn adapter
+
+Configure like this:
+
+	log := NewLogger(1000)
+	log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
+	log.Info("info")
+
+
+## Smtp adapter
+
+Configure like this:
+
+	log := NewLogger(10000)
+	log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
+	log.Critical("sendmail critical")
+	time.Sleep(time.Second * 30)

+ 116 - 0
Godeps/_workspace/src/github.com/astaxie/beego/logs/conn.go

@@ -0,0 +1,116 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// 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.
+
+package logs
+
+import (
+	"encoding/json"
+	"io"
+	"log"
+	"net"
+)
+
+// ConnWriter implements LoggerInterface.
+// it writes messages in keep-live tcp connection.
+type ConnWriter struct {
+	lg             *log.Logger
+	innerWriter    io.WriteCloser
+	ReconnectOnMsg bool   `json:"reconnectOnMsg"`
+	Reconnect      bool   `json:"reconnect"`
+	Net            string `json:"net"`
+	Addr           string `json:"addr"`
+	Level          int    `json:"level"`
+}
+
+// create new ConnWrite returning as LoggerInterface.
+func NewConn() LoggerInterface {
+	conn := new(ConnWriter)
+	conn.Level = LevelTrace
+	return conn
+}
+
+// init connection writer with json config.
+// json config only need key "level".
+func (c *ConnWriter) Init(jsonconfig string) error {
+	return json.Unmarshal([]byte(jsonconfig), c)
+}
+
+// write message in connection.
+// if connection is down, try to re-connect.
+func (c *ConnWriter) WriteMsg(msg string, level int) error {
+	if level > c.Level {
+		return nil
+	}
+	if c.neddedConnectOnMsg() {
+		err := c.connect()
+		if err != nil {
+			return err
+		}
+	}
+
+	if c.ReconnectOnMsg {
+		defer c.innerWriter.Close()
+	}
+	c.lg.Println(msg)
+	return nil
+}
+
+// implementing method. empty.
+func (c *ConnWriter) Flush() {
+
+}
+
+// destroy connection writer and close tcp listener.
+func (c *ConnWriter) Destroy() {
+	if c.innerWriter != nil {
+		c.innerWriter.Close()
+	}
+}
+
+func (c *ConnWriter) connect() error {
+	if c.innerWriter != nil {
+		c.innerWriter.Close()
+		c.innerWriter = nil
+	}
+
+	conn, err := net.Dial(c.Net, c.Addr)
+	if err != nil {
+		return err
+	}
+
+	if tcpConn, ok := conn.(*net.TCPConn); ok {
+		tcpConn.SetKeepAlive(true)
+	}
+
+	c.innerWriter = conn
+	c.lg = log.New(conn, "", log.Ldate|log.Ltime)
+	return nil
+}
+
+func (c *ConnWriter) neddedConnectOnMsg() bool {
+	if c.Reconnect {
+		c.Reconnect = false
+		return true
+	}
+
+	if c.innerWriter == nil {
+		return true
+	}
+
+	return c.ReconnectOnMsg
+}
+
+func init() {
+	Register("conn", NewConn)
+}

+ 95 - 0
Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go

@@ -0,0 +1,95 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// 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.
+
+package logs
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+	"runtime"
+)
+
+type Brush func(string) string
+
+func NewBrush(color string) Brush {
+	pre := "\033["
+	reset := "\033[0m"
+	return func(text string) string {
+		return pre + color + "m" + text + reset
+	}
+}
+
+var colors = []Brush{
+	NewBrush("1;37"), // Emergency	white
+	NewBrush("1;36"), // Alert			cyan
+	NewBrush("1;35"), // Critical   magenta
+	NewBrush("1;31"), // Error      red
+	NewBrush("1;33"), // Warning    yellow
+	NewBrush("1;32"), // Notice			green
+	NewBrush("1;34"), // Informational	blue
+	NewBrush("1;34"), // Debug      blue
+}
+
+// ConsoleWriter implements LoggerInterface and writes messages to terminal.
+type ConsoleWriter struct {
+	lg    *log.Logger
+	Level int `json:"level"`
+}
+
+// create ConsoleWriter returning as LoggerInterface.
+func NewConsole() LoggerInterface {
+	cw := &ConsoleWriter{
+		lg:    log.New(os.Stdout, "", log.Ldate|log.Ltime),
+		Level: LevelDebug,
+	}
+	return cw
+}
+
+// init console logger.
+// jsonconfig like '{"level":LevelTrace}'.
+func (c *ConsoleWriter) Init(jsonconfig string) error {
+	if len(jsonconfig) == 0 {
+		return nil
+	}
+	return json.Unmarshal([]byte(jsonconfig), c)
+}
+
+// write message in console.
+func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
+	if level > c.Level {
+		return nil
+	}
+	if goos := runtime.GOOS; goos == "windows" {
+		c.lg.Println(msg)
+		return nil
+	}
+	c.lg.Println(colors[level](msg))
+
+	return nil
+}
+
+// implementing method. empty.
+func (c *ConsoleWriter) Destroy() {
+
+}
+
+// implementing method. empty.
+func (c *ConsoleWriter) Flush() {
+
+}
+
+func init() {
+	Register("console", NewConsole)
+}

+ 76 - 0
Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go

@@ -0,0 +1,76 @@
+package es
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net"
+	"net/url"
+	"time"
+
+	"github.com/astaxie/beego/logs"
+	"github.com/belogik/goes"
+)
+
+func NewES() logs.LoggerInterface {
+	cw := &esLogger{
+		Level: logs.LevelDebug,
+	}
+	return cw
+}
+
+type esLogger struct {
+	*goes.Connection
+	DSN   string `json:"dsn"`
+	Level int    `json:"level"`
+}
+
+// {"dsn":"http://localhost:9200/","level":1}
+func (el *esLogger) Init(jsonconfig string) error {
+	err := json.Unmarshal([]byte(jsonconfig), el)
+	if err != nil {
+		return err
+	}
+	if el.DSN == "" {
+		return errors.New("empty dsn")
+	} else if u, err := url.Parse(el.DSN); err != nil {
+		return err
+	} else if u.Path == "" {
+		return errors.New("missing prefix")
+	} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
+		return err
+	} else {
+		conn := goes.NewConnection(host, port)
+		el.Connection = conn
+	}
+	return nil
+}
+
+func (el *esLogger) WriteMsg(msg string, level int) error {
+	if level > el.Level {
+		return nil
+	}
+	t := time.Now()
+	vals := make(map[string]interface{})
+	vals["@timestamp"] = t.Format(time.RFC3339)
+	vals["@msg"] = msg
+	d := goes.Document{
+		Index:  fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
+		Type:   "logs",
+		Fields: vals,
+	}
+	_, err := el.Index(d, nil)
+	return err
+}
+
+func (el *esLogger) Destroy() {
+
+}
+
+func (el *esLogger) Flush() {
+
+}
+
+func init() {
+	logs.Register("es", NewES)
+}

+ 283 - 0
Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go

@@ -0,0 +1,283 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// 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.
+
+package logs
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+	"time"
+)
+
+// FileLogWriter implements LoggerInterface.
+// It writes messages by lines limit, file size limit, or time frequency.
+type FileLogWriter struct {
+	*log.Logger
+	mw *MuxWriter
+	// The opened file
+	Filename string `json:"filename"`
+
+	Maxlines          int `json:"maxlines"`
+	maxlines_curlines int
+
+	// Rotate at size
+	Maxsize         int `json:"maxsize"`
+	maxsize_cursize int
+
+	// Rotate daily
+	Daily          bool  `json:"daily"`
+	Maxdays        int64 `json:"maxdays"`
+	daily_opendate int
+
+	Rotate bool `json:"rotate"`
+
+	startLock sync.Mutex // Only one log can write to the file
+
+	Level int `json:"level"`
+}
+
+// an *os.File writer with locker.
+type MuxWriter struct {
+	sync.Mutex
+	fd *os.File
+}
+
+// write to os.File.
+func (l *MuxWriter) Write(b []byte) (int, error) {
+	l.Lock()
+	defer l.Unlock()
+	return l.fd.Write(b)
+}
+
+// set os.File in writer.
+func (l *MuxWriter) SetFd(fd *os.File) {
+	if l.fd != nil {
+		l.fd.Close()
+	}
+	l.fd = fd
+}
+
+// create a FileLogWriter returning as LoggerInterface.
+func NewFileWriter() LoggerInterface {
+	w := &FileLogWriter{
+		Filename: "",
+		Maxlines: 1000000,
+		Maxsize:  1 << 28, //256 MB
+		Daily:    true,
+		Maxdays:  7,
+		Rotate:   true,
+		Level:    LevelTrace,
+	}
+	// use MuxWriter instead direct use os.File for lock write when rotate
+	w.mw = new(MuxWriter)
+	// set MuxWriter as Logger's io.Writer
+	w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
+	return w
+}
+
+// Init file logger with json config.
+// jsonconfig like:
+//	{
+//	"filename":"logs/beego.log",
+//	"maxlines":10000,
+//	"maxsize":1<<30,
+//	"daily":true,
+//	"maxdays":15,
+//	"rotate":true
+//	}
+func (w *FileLogWriter) Init(jsonconfig string) error {
+	err := json.Unmarshal([]byte(jsonconfig), w)
+	if err != nil {
+		return err
+	}
+	if len(w.Filename) == 0 {
+		return errors.New("jsonconfig must have filename")
+	}
+	err = w.startLogger()
+	return err
+}
+
+// start file logger. create log file and set to locker-inside file writer.
+func (w *FileLogWriter) startLogger() error {
+	fd, err := w.createLogFile()
+	if err != nil {
+		return err
+	}
+	w.mw.SetFd(fd)
+	return w.initFd()
+}
+
+func (w *FileLogWriter) docheck(size int) {
+	w.startLock.Lock()
+	defer w.startLock.Unlock()
+	if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
+		(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
+		(w.Daily && time.Now().Day() != w.daily_opendate)) {
+		if err := w.DoRotate(); err != nil {
+			fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
+			return
+		}
+	}
+	w.maxlines_curlines++
+	w.maxsize_cursize += size
+}
+
+// write logger message into file.
+func (w *FileLogWriter) WriteMsg(msg string, level int) error {
+	if level > w.Level {
+		return nil
+	}
+	n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
+	w.docheck(n)
+	w.Logger.Println(msg)
+	return nil
+}
+
+func (w *FileLogWriter) createLogFile() (*os.File, error) {
+	// Open the log file
+	fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
+	return fd, err
+}
+
+func (w *FileLogWriter) initFd() error {
+	fd := w.mw.fd
+	finfo, err := fd.Stat()
+	if err != nil {
+		return fmt.Errorf("get stat err: %s\n", err)
+	}
+	w.maxsize_cursize = int(finfo.Size())
+	w.daily_opendate = time.Now().Day()
+	w.maxlines_curlines = 0
+	if finfo.Size() > 0 {
+		count, err := w.lines()
+		if err != nil {
+			return err
+		}
+		w.maxlines_curlines = count
+	}
+	return nil
+}
+
+func (w *FileLogWriter) lines() (int, error) {
+	fd, err := os.Open(w.Filename)
+	if err != nil {
+		return 0, err
+	}
+	defer fd.Close()
+
+	buf := make([]byte, 32768) // 32k
+	count := 0
+	lineSep := []byte{'\n'}
+
+	for {
+		c, err := fd.Read(buf)
+		if err != nil && err != io.EOF {
+			return count, err
+		}
+
+		count += bytes.Count(buf[:c], lineSep)
+
+		if err == io.EOF {
+			break
+		}
+	}
+
+	return count, nil
+}
+
+// DoRotate means it need to write file in new file.
+// new file name like xx.log.2013-01-01.2
+func (w *FileLogWriter) DoRotate() error {
+	_, err := os.Lstat(w.Filename)
+	if err == nil { // file exists
+		// Find the next available number
+		num := 1
+		fname := ""
+		for ; err == nil && num <= 999; num++ {
+			fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
+			_, err = os.Lstat(fname)
+		}
+		// return error if the last file checked still existed
+		if err == nil {
+			return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
+		}
+
+		// block Logger's io.Writer
+		w.mw.Lock()
+		defer w.mw.Unlock()
+
+		fd := w.mw.fd
+		fd.Close()
+
+		// close fd before rename
+		// Rename the file to its newfound home
+		err = os.Rename(w.Filename, fname)
+		if err != nil {
+			return fmt.Errorf("Rotate: %s\n", err)
+		}
+
+		// re-start logger
+		err = w.startLogger()
+		if err != nil {
+			return fmt.Errorf("Rotate StartLogger: %s\n", err)
+		}
+
+		go w.deleteOldLog()
+	}
+
+	return nil
+}
+
+func (w *FileLogWriter) deleteOldLog() {
+	dir := filepath.Dir(w.Filename)
+	filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
+		defer func() {
+			if r := recover(); r != nil {
+				returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
+				fmt.Println(returnErr)
+			}
+		}()
+
+		if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) {
+			if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
+				os.Remove(path)
+			}
+		}
+		return
+	})
+}
+
+// destroy file logger, close file writer.
+func (w *FileLogWriter) Destroy() {
+	w.mw.fd.Close()
+}
+
+// flush file logger.
+// there are no buffering messages in file logger in memory.
+// flush file means sync file from disk.
+func (w *FileLogWriter) Flush() {
+	w.mw.fd.Sync()
+}
+
+func init() {
+	Register("file", NewFileWriter)
+}

+ 350 - 0
Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go

@@ -0,0 +1,350 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// 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.
+
+// Usage:
+//
+// import "github.com/astaxie/beego/logs"
+//
+//	log := NewLogger(10000)
+//	log.SetLogger("console", "")
+//
+//	> the first params stand for how many channel
+//
+// Use it like this:
+//
+//	log.Trace("trace")
+//	log.Info("info")
+//	log.Warn("warning")
+//	log.Debug("debug")
+//	log.Critical("critical")
+//
+//  more docs http://beego.me/docs/module/logs.md
+package logs
+
+import (
+	"fmt"
+	"path"
+	"runtime"
+	"sync"
+)
+
+// RFC5424 log message levels.
+const (
+	LevelEmergency = iota
+	LevelAlert
+	LevelCritical
+	LevelError
+	LevelWarning
+	LevelNotice
+	LevelInformational
+	LevelDebug
+)
+
+// Legacy loglevel constants to ensure backwards compatibility.
+//
+// Deprecated: will be removed in 1.5.0.
+const (
+	LevelInfo  = LevelInformational
+	LevelTrace = LevelDebug
+	LevelWarn  = LevelWarning
+)
+
+type loggerType func() LoggerInterface
+
+// LoggerInterface defines the behavior of a log provider.
+type LoggerInterface interface {
+	Init(config string) error
+	WriteMsg(msg string, level int) error
+	Destroy()
+	Flush()
+}
+
+var adapters = make(map[string]loggerType)
+
+// Register makes a log provide available by the provided name.
+// If Register is called twice with the same name or if driver is nil,
+// it panics.
+func Register(name string, log loggerType) {
+	if log == nil {
+		panic("logs: Register provide is nil")
+	}
+	if _, dup := adapters[name]; dup {
+		panic("logs: Register called twice for provider " + name)
+	}
+	adapters[name] = log
+}
+
+// BeeLogger is default logger in beego application.
+// it can contain several providers and log message into all providers.
+type BeeLogger struct {
+	lock                sync.Mutex
+	level               int
+	enableFuncCallDepth bool
+	loggerFuncCallDepth int
+	asynchronous        bool
+	msg                 chan *logMsg
+	outputs             map[string]LoggerInterface
+}
+
+type logMsg struct {
+	level int
+	msg   string
+}
+
+// NewLogger returns a new BeeLogger.
+// channellen means the number of messages in chan.
+// if the buffering chan is full, logger adapters write to file or other way.
+func NewLogger(channellen int64) *BeeLogger {
+	bl := new(BeeLogger)
+	bl.level = LevelDebug
+	bl.loggerFuncCallDepth = 2
+	bl.msg = make(chan *logMsg, channellen)
+	bl.outputs = make(map[string]LoggerInterface)
+	return bl
+}
+
+func (bl *BeeLogger) Async() *BeeLogger {
+	bl.asynchronous = true
+	go bl.startLogger()
+	return bl
+}
+
+// SetLogger provides a given logger adapter into BeeLogger with config string.
+// config need to be correct JSON as string: {"interval":360}.
+func (bl *BeeLogger) SetLogger(adaptername string, config string) error {
+	bl.lock.Lock()
+	defer bl.lock.Unlock()
+	if log, ok := adapters[adaptername]; ok {
+		lg := log()
+		err := lg.Init(config)
+		bl.outputs[adaptername] = lg
+		if err != nil {
+			fmt.Println("logs.BeeLogger.SetLogger: " + err.Error())
+			return err
+		}
+	} else {
+		return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
+	}
+	return nil
+}
+
+// remove a logger adapter in BeeLogger.
+func (bl *BeeLogger) DelLogger(adaptername string) error {
+	bl.lock.Lock()
+	defer bl.lock.Unlock()
+	if lg, ok := bl.outputs[adaptername]; ok {
+		lg.Destroy()
+		delete(bl.outputs, adaptername)
+		return nil
+	} else {
+		return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
+	}
+}
+
+func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
+	lm := new(logMsg)
+	lm.level = loglevel
+	if bl.enableFuncCallDepth {
+		_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
+		if !ok {
+			file = "???"
+			line = 0
+		}
+		_, filename := path.Split(file)
+		lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
+	} else {
+		lm.msg = msg
+	}
+	if bl.asynchronous {
+		bl.msg <- lm
+	} else {
+		for name, l := range bl.outputs {
+			err := l.WriteMsg(lm.msg, lm.level)
+			if err != nil {
+				fmt.Println("unable to WriteMsg to adapter:", name, err)
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+// Set log message level.
+//
+// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
+// log providers will not even be sent the message.
+func (bl *BeeLogger) SetLevel(l int) {
+	bl.level = l
+}
+
+// set log funcCallDepth
+func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
+	bl.loggerFuncCallDepth = d
+}
+
+// get log funcCallDepth for wrapper
+func (bl *BeeLogger) GetLogFuncCallDepth() int {
+	return bl.loggerFuncCallDepth
+}
+
+// enable log funcCallDepth
+func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
+	bl.enableFuncCallDepth = b
+}
+
+// start logger chan reading.
+// when chan is not empty, write logs.
+func (bl *BeeLogger) startLogger() {
+	for {
+		select {
+		case bm := <-bl.msg:
+			for _, l := range bl.outputs {
+				err := l.WriteMsg(bm.msg, bm.level)
+				if err != nil {
+					fmt.Println("ERROR, unable to WriteMsg:", err)
+				}
+			}
+		}
+	}
+}
+
+// Log EMERGENCY level message.
+func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
+	if LevelEmergency > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[M] "+format, v...)
+	bl.writerMsg(LevelEmergency, msg)
+}
+
+// Log ALERT level message.
+func (bl *BeeLogger) Alert(format string, v ...interface{}) {
+	if LevelAlert > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[A] "+format, v...)
+	bl.writerMsg(LevelAlert, msg)
+}
+
+// Log CRITICAL level message.
+func (bl *BeeLogger) Critical(format string, v ...interface{}) {
+	if LevelCritical > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[C] "+format, v...)
+	bl.writerMsg(LevelCritical, msg)
+}
+
+// Log ERROR level message.
+func (bl *BeeLogger) Error(format string, v ...interface{}) {
+	if LevelError > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[E] "+format, v...)
+	bl.writerMsg(LevelError, msg)
+}
+
+// Log WARNING level message.
+func (bl *BeeLogger) Warning(format string, v ...interface{}) {
+	if LevelWarning > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[W] "+format, v...)
+	bl.writerMsg(LevelWarning, msg)
+}
+
+// Log NOTICE level message.
+func (bl *BeeLogger) Notice(format string, v ...interface{}) {
+	if LevelNotice > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[N] "+format, v...)
+	bl.writerMsg(LevelNotice, msg)
+}
+
+// Log INFORMATIONAL level message.
+func (bl *BeeLogger) Informational(format string, v ...interface{}) {
+	if LevelInformational > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[I] "+format, v...)
+	bl.writerMsg(LevelInformational, msg)
+}
+
+// Log DEBUG level message.
+func (bl *BeeLogger) Debug(format string, v ...interface{}) {
+	if LevelDebug > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[D] "+format, v...)
+	bl.writerMsg(LevelDebug, msg)
+}
+
+// Log WARN level message.
+// compatibility alias for Warning()
+func (bl *BeeLogger) Warn(format string, v ...interface{}) {
+	if LevelWarning > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[W] "+format, v...)
+	bl.writerMsg(LevelWarning, msg)
+}
+
+// Log INFO level message.
+// compatibility alias for Informational()
+func (bl *BeeLogger) Info(format string, v ...interface{}) {
+	if LevelInformational > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[I] "+format, v...)
+	bl.writerMsg(LevelInformational, msg)
+}
+
+// Log TRACE level message.
+// compatibility alias for Debug()
+func (bl *BeeLogger) Trace(format string, v ...interface{}) {
+	if LevelDebug > bl.level {
+		return
+	}
+	msg := fmt.Sprintf("[D] "+format, v...)
+	bl.writerMsg(LevelDebug, msg)
+}
+
+// flush all chan data.
+func (bl *BeeLogger) Flush() {
+	for _, l := range bl.outputs {
+		l.Flush()
+	}
+}
+
+// close logger, flush all chan data and destroy all adapters in BeeLogger.
+func (bl *BeeLogger) Close() {
+	for {
+		if len(bl.msg) > 0 {
+			bm := <-bl.msg
+			for _, l := range bl.outputs {
+				err := l.WriteMsg(bm.msg, bm.level)
+				if err != nil {
+					fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err)
+				}
+			}
+			continue
+		}
+		break
+	}
+	for _, l := range bl.outputs {
+		l.Flush()
+		l.Destroy()
+	}
+}

+ 165 - 0
Godeps/_workspace/src/github.com/astaxie/beego/logs/smtp.go

@@ -0,0 +1,165 @@
+// Copyright 2014 beego Author. All Rights Reserved.
+//
+// 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.
+
+package logs
+
+import (
+	"crypto/tls"
+	"encoding/json"
+	"fmt"
+	"net"
+	"net/smtp"
+	"strings"
+	"time"
+)
+
+const (
+// no usage
+// subjectPhrase = "Diagnostic message from server"
+)
+
+// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server.
+type SmtpWriter struct {
+	Username           string   `json:"Username"`
+	Password           string   `json:"password"`
+	Host               string   `json:"Host"`
+	Subject            string   `json:"subject"`
+	FromAddress        string   `json:"fromAddress"`
+	RecipientAddresses []string `json:"sendTos"`
+	Level              int      `json:"level"`
+}
+
+// create smtp writer.
+func NewSmtpWriter() LoggerInterface {
+	return &SmtpWriter{Level: LevelTrace}
+}
+
+// init smtp writer with json config.
+// config like:
+//	{
+//		"Username":"example@gmail.com",
+//		"password:"password",
+//		"host":"smtp.gmail.com:465",
+//		"subject":"email title",
+//		"fromAddress":"from@example.com",
+//		"sendTos":["email1","email2"],
+//		"level":LevelError
+//	}
+func (s *SmtpWriter) Init(jsonconfig string) error {
+	err := json.Unmarshal([]byte(jsonconfig), s)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth {
+	if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
+		return nil
+	}
+	return smtp.PlainAuth(
+		"",
+		s.Username,
+		s.Password,
+		host,
+	)
+}
+
+func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
+	client, err := smtp.Dial(hostAddressWithPort)
+	if err != nil {
+		return err
+	}
+
+	host, _, _ := net.SplitHostPort(hostAddressWithPort)
+	tlsConn := &tls.Config{
+		InsecureSkipVerify: true,
+		ServerName:         host,
+	}
+	if err = client.StartTLS(tlsConn); err != nil {
+		return err
+	}
+
+	if auth != nil {
+		if err = client.Auth(auth); err != nil {
+			return err
+		}
+	}
+
+	if err = client.Mail(fromAddress); err != nil {
+		return err
+	}
+
+	for _, rec := range recipients {
+		if err = client.Rcpt(rec); err != nil {
+			return err
+		}
+	}
+
+	w, err := client.Data()
+	if err != nil {
+		return err
+	}
+	_, err = w.Write([]byte(msgContent))
+	if err != nil {
+		return err
+	}
+
+	err = w.Close()
+	if err != nil {
+		return err
+	}
+
+	err = client.Quit()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// write message in smtp writer.
+// it will send an email with subject and only this message.
+func (s *SmtpWriter) WriteMsg(msg string, level int) error {
+	if level > s.Level {
+		return nil
+	}
+
+	hp := strings.Split(s.Host, ":")
+
+	// Set up authentication information.
+	auth := s.GetSmtpAuth(hp[0])
+
+	// Connect to the server, authenticate, set the sender and recipient,
+	// and send the email all in one step.
+	content_type := "Content-Type: text/plain" + "; charset=UTF-8"
+	mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
+		">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
+
+	return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
+}
+
+// implementing method. empty.
+func (s *SmtpWriter) Flush() {
+	return
+}
+
+// implementing method. empty.
+func (s *SmtpWriter) Destroy() {
+	return
+}
+
+func init() {
+	Register("smtp", NewSmtpWriter)
+}

+ 19 - 0
Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2011-2014 Dmitry Chestnykh <dmitry@codingrobots.com>
+
+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.

+ 14 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE

@@ -0,0 +1,14 @@
+Copyright (c) 2013 Vaughan Newton
+
+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.

+ 70 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md

@@ -0,0 +1,70 @@
+go-ini
+======
+
+INI parsing library for Go (golang).
+
+View the API documentation [here](http://godoc.org/github.com/vaughan0/go-ini).
+
+Usage
+-----
+
+Parse an INI file:
+
+```go
+import "github.com/vaughan0/go-ini"
+
+file, err := ini.LoadFile("myfile.ini")
+```
+
+Get data from the parsed file:
+
+```go
+name, ok := file.Get("person", "name")
+if !ok {
+  panic("'name' variable missing from 'person' section")
+}
+```
+
+Iterate through values in a section:
+
+```go
+for key, value := range file["mysection"] {
+  fmt.Printf("%s => %s\n", key, value)
+}
+```
+
+Iterate through sections in a file:
+
+```go
+for name, section := range file {
+  fmt.Printf("Section name: %s\n", name)
+}
+```
+
+File Format
+-----------
+
+INI files are parsed by go-ini line-by-line. Each line may be one of the following:
+
+  * A section definition: [section-name]
+  * A property: key = value
+  * A comment: #blahblah _or_ ;blahblah
+  * Blank. The line will be ignored.
+
+Properties defined before any section headers are placed in the default section, which has
+the empty string as it's key.
+
+Example:
+
+```ini
+# I am a comment
+; So am I!
+
+[apples]
+colour = red or green
+shape = applish
+
+[oranges]
+shape = square
+colour = blue
+```

+ 123 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go

@@ -0,0 +1,123 @@
+// Package ini provides functions for parsing INI configuration files.
+package ini
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"regexp"
+	"strings"
+)
+
+var (
+	sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
+	assignRegex  = regexp.MustCompile(`^([^=]+)=(.*)$`)
+)
+
+// ErrSyntax is returned when there is a syntax error in an INI file.
+type ErrSyntax struct {
+	Line   int
+	Source string // The contents of the erroneous line, without leading or trailing whitespace
+}
+
+func (e ErrSyntax) Error() string {
+	return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
+}
+
+// A File represents a parsed INI file.
+type File map[string]Section
+
+// A Section represents a single section of an INI file.
+type Section map[string]string
+
+// Returns a named Section. A Section will be created if one does not already exist for the given name.
+func (f File) Section(name string) Section {
+	section := f[name]
+	if section == nil {
+		section = make(Section)
+		f[name] = section
+	}
+	return section
+}
+
+// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
+func (f File) Get(section, key string) (value string, ok bool) {
+	if s := f[section]; s != nil {
+		value, ok = s[key]
+	}
+	return
+}
+
+// Loads INI data from a reader and stores the data in the File.
+func (f File) Load(in io.Reader) (err error) {
+	bufin, ok := in.(*bufio.Reader)
+	if !ok {
+		bufin = bufio.NewReader(in)
+	}
+	return parseFile(bufin, f)
+}
+
+// Loads INI data from a named file and stores the data in the File.
+func (f File) LoadFile(file string) (err error) {
+	in, err := os.Open(file)
+	if err != nil {
+		return
+	}
+	defer in.Close()
+	return f.Load(in)
+}
+
+func parseFile(in *bufio.Reader, file File) (err error) {
+	section := ""
+	lineNum := 0
+	for done := false; !done; {
+		var line string
+		if line, err = in.ReadString('\n'); err != nil {
+			if err == io.EOF {
+				done = true
+			} else {
+				return
+			}
+		}
+		lineNum++
+		line = strings.TrimSpace(line)
+		if len(line) == 0 {
+			// Skip blank lines
+			continue
+		}
+		if line[0] == ';' || line[0] == '#' {
+			// Skip comments
+			continue
+		}
+
+		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
+			key, val := groups[1], groups[2]
+			key, val = strings.TrimSpace(key), strings.TrimSpace(val)
+			file.Section(section)[key] = val
+		} else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
+			name := strings.TrimSpace(groups[1])
+			section = name
+			// Create the section if it does not exist
+			file.Section(section)
+		} else {
+			return ErrSyntax{lineNum, line}
+		}
+
+	}
+	return nil
+}
+
+// Loads and returns a File from a reader.
+func Load(in io.Reader) (File, error) {
+	file := make(File)
+	err := file.Load(in)
+	return file, err
+}
+
+// Loads and returns an INI File from a file on disk.
+func LoadFile(filename string) (File, error) {
+	file := make(File)
+	err := file.LoadFile(filename)
+	return file, err
+}

+ 2 - 0
Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini

@@ -0,0 +1,2 @@
+[default]
+stuff = things

+ 15 - 0
Makefile

@@ -0,0 +1,15 @@
+export PATH := $(GOPATH)/bin:$(PATH)
+
+all: build
+
+build: godep frps frpc
+
+godep:
+	@go get github.com/tools/godep
+	godep restore
+
+frps:
+	godep go build -o bin/frps ./cmd/frps
+
+frpc:
+	godep go build -o bin/frpc ./cmd/frpc

+ 89 - 0
cmd/frpc/config.go

@@ -0,0 +1,89 @@
+package main
+
+import (
+	"fmt"
+	"strconv"
+
+	"frp/pkg/models"
+
+	ini "github.com/vaughan0/go-ini"
+)
+
+// common config
+var (
+	ServerAddr	string = "0.0.0.0"
+	ServerPort	int64  = 7000
+	LogFile		string = "./frpc.log"
+	LogLevel	string = "warn"
+	LogWay		string = "file"
+)
+
+var ProxyClients map[string]*models.ProxyClient = make(map[string]*models.ProxyClient)
+
+
+func LoadConf(confFile string) (err error) {
+	var tmpStr string
+	var ok bool
+
+	conf, err := ini.LoadFile(confFile)
+	if err != nil {
+		return err
+	}
+
+	// common
+	tmpStr, ok = conf.Get("common", "server_addr")
+	if ok {
+		ServerAddr = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "server_port")
+	if ok {
+		ServerPort, _ = strconv.ParseInt(tmpStr, 10, 64)
+	}
+
+	tmpStr, ok = conf.Get("common", "log_file")
+	if ok {
+		LogFile = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "log_level")
+	if ok {
+		LogLevel = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "log_way")
+	if ok {
+		LogWay = tmpStr
+	}
+
+	// servers
+	for name, section := range conf {
+		if name != "common" {
+			proxyClient := &models.ProxyClient{}
+			proxyClient.Name = name
+
+			proxyClient.Passwd, ok = section["passwd"]
+			if !ok {
+				return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyClient.Name)
+			}
+
+			portStr, ok := section["local_port"]
+			if ok {
+				proxyClient.LocalPort, err = strconv.ParseInt(portStr, 10, 64)
+				if err != nil {
+					return fmt.Errorf("Parse ini file error: proxy [%s] local_port error", proxyClient.Name)
+				}
+			} else {
+				return fmt.Errorf("Parse ini file error: proxy [%s] local_port not found", proxyClient.Name)
+			}
+
+			ProxyClients[proxyClient.Name] = proxyClient
+		}
+	}
+
+	if len(ProxyClients) == 0 {
+		return fmt.Errorf("Parse ini file error: no proxy config found")
+	}
+
+	return nil
+}

+ 67 - 0
cmd/frpc/control.go

@@ -0,0 +1,67 @@
+package main
+
+import (
+	"io"
+	"sync"
+	"encoding/json"
+
+	"frp/pkg/models"
+	"frp/pkg/utils/conn"
+	"frp/pkg/utils/log"
+)
+
+func ControlProcess(cli *models.ProxyClient, wait *sync.WaitGroup) {
+	defer wait.Done()
+
+	c := &conn.Conn{}
+	err := c.ConnectServer(ServerAddr, ServerPort)
+	if err != nil {
+		log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", cli.Name, ServerAddr, ServerPort, err)
+		return
+	}
+	defer c.Close()
+
+	req := &models.ClientCtlReq{
+		Type:		models.ControlConn,
+		ProxyName:	cli.Name,
+		Passwd:		cli.Passwd,
+	}
+	buf, _ := json.Marshal(req)
+	err = c.Write(string(buf) + "\n")
+	if err != nil {
+		log.Error("ProxyName [%s], write to server error, %v", cli.Name, err)
+		return
+	}
+
+	res, err := c.ReadLine()
+	if err != nil {
+		log.Error("ProxyName [%s], read from server error, %v", cli.Name, err)
+		return
+	}
+	log.Debug("ProxyName [%s], read [%s]", cli.Name, res)
+
+	clientCtlRes := &models.ClientCtlRes{}
+	if err = json.Unmarshal([]byte(res), &clientCtlRes); err != nil {
+		log.Error("ProxyName [%s], format server response error, %v", cli.Name, err)
+		return
+	}
+
+	if clientCtlRes.Code != 0 {
+		log.Error("ProxyName [%s], start proxy error, %s", cli.Name, clientCtlRes.Msg)
+		return
+	}
+
+	for {
+		// ignore response content now
+		_, err := c.ReadLine()
+		if err == io.EOF {
+			log.Debug("ProxyName [%s], server close this control conn", cli.Name)
+			break
+		} else if err != nil {
+			log.Warn("ProxyName [%s], read from server error, %v", cli.Name, err)
+			continue
+		}
+
+		cli.StartTunnel(ServerAddr, ServerPort)
+	}
+}

+ 30 - 0
cmd/frpc/main.go

@@ -0,0 +1,30 @@
+package main
+
+import (
+	"os"
+	"sync"
+	
+	"frp/pkg/utils/log"
+)
+
+func main() {
+	err := LoadConf("./frpc.ini")
+	if err != nil {
+		os.Exit(-1)
+	}
+
+	log.InitLog(LogWay, LogFile, LogLevel)
+
+	// wait until all control goroutine exit
+	var wait sync.WaitGroup
+	wait.Add(len(ProxyClients))
+
+	for _, client := range ProxyClients {
+		go ControlProcess(client, &wait)
+	}
+
+	log.Info("Start frpc success")
+
+	wait.Wait()
+	log.Warn("All proxy exit!")
+}

+ 95 - 0
cmd/frps/config.go

@@ -0,0 +1,95 @@
+package main
+
+import (
+	"fmt"
+	"strconv"
+
+	"frp/pkg/models"
+
+	ini "github.com/vaughan0/go-ini"
+)
+
+// common config
+var (
+	BindAddr	string = "0.0.0.0"
+	BindPort	int64  = 9527
+	LogFile		string = "./frps.log"
+	LogLevel	string = "warn"
+	LogWay		string = "file"
+)
+
+var ProxyServers map[string]*models.ProxyServer = make(map[string]*models.ProxyServer)
+
+
+func LoadConf(confFile string) (err error) {
+	var tmpStr string
+	var ok bool
+
+	conf, err := ini.LoadFile(confFile)
+	if err != nil {
+		return err
+	}
+
+	// common
+	tmpStr, ok = conf.Get("common", "bind_addr")
+	if ok {
+		BindAddr = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "bind_port")
+	if ok {
+		BindPort, _ = strconv.ParseInt(tmpStr, 10, 64)
+	}
+
+	tmpStr, ok = conf.Get("common", "log_file")
+	if ok {
+		LogFile = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "log_level")
+	if ok {
+		LogLevel = tmpStr
+	}
+
+	tmpStr, ok = conf.Get("common", "log_way")
+	if ok {
+		LogWay = tmpStr
+	}
+
+	// servers
+	for name, section := range conf {
+		if name != "common" {
+			proxyServer := &models.ProxyServer{}
+			proxyServer.Name = name
+
+			proxyServer.Passwd, ok = section["passwd"]
+			if !ok {
+				return fmt.Errorf("Parse ini file error: proxy [%s] no passwd found", proxyServer.Name)
+			}
+
+			proxyServer.BindAddr, ok = section["bind_addr"]
+			if !ok {
+				proxyServer.BindAddr = "0.0.0.0"
+			}
+
+			portStr, ok := section["listen_port"]
+			if ok {
+				proxyServer.ListenPort, err = strconv.ParseInt(portStr, 10, 64)
+				if err != nil {
+					return fmt.Errorf("Parse ini file error: proxy [%s] listen_port error", proxyServer.Name)
+				}
+			} else {
+				return fmt.Errorf("Parse ini file error: proxy [%s] listen_port not found", proxyServer.Name)
+			}
+
+			proxyServer.Init()
+			ProxyServers[proxyServer.Name] = proxyServer
+		}
+	}
+
+	if len(ProxyServers) == 0 {
+		return fmt.Errorf("Parse ini file error: no proxy config found")
+	}
+
+	return nil
+}

+ 134 - 0
cmd/frps/control.go

@@ -0,0 +1,134 @@
+package main
+
+import (
+	"fmt"
+	"encoding/json"
+
+	"frp/pkg/utils/log"
+	"frp/pkg/utils/conn"
+	"frp/pkg/models"
+)
+
+func ProcessControlConn(l *conn.Listener) {
+	for {
+		c := l.GetConn()
+		log.Debug("Get one new conn, %v", c.GetRemoteAddr())
+		go controlWorker(c)
+	}
+}
+
+// control connection from every client and server
+func controlWorker(c *conn.Conn) {
+	// the first message is from client to server
+	// if error, close connection
+	res, err := c.ReadLine()
+	if err != nil {
+		log.Warn("Read error, %v", err)
+		return
+	}
+	log.Debug("get: %s", res)
+
+	clientCtlReq := &models.ClientCtlReq{}
+	clientCtlRes := &models.ClientCtlRes{}
+	if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil {
+		log.Warn("Parse err: %v : %s", err, res)
+		return
+	}
+
+	// check
+	succ, msg, needRes := checkProxy(clientCtlReq, c)
+	if !succ {
+		clientCtlRes.Code = 1
+		clientCtlRes.Msg = msg
+	}
+	
+	if needRes {
+		buf, _ := json.Marshal(clientCtlRes)
+		err = c.Write(string(buf) + "\n")
+		if err != nil {
+			log.Warn("Write error, %v", err)
+		}
+	} else {
+	// work conn, just return
+		return
+	}
+
+	defer c.Close()
+	// others is from server to client
+	server, ok := ProxyServers[clientCtlReq.ProxyName]
+	if !ok {
+		log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName)
+		return
+	}
+
+	serverCtlReq := &models.ClientCtlReq{}
+	serverCtlReq.Type = models.WorkConn
+	for {
+		server.WaitUserConn()
+		buf, _ := json.Marshal(serverCtlReq)
+		err = c.Write(string(buf) + "\n")
+		if err != nil {
+			log.Warn("ProxyName [%s], write to client error, proxy exit", server.Name)
+			server.Close()
+			return
+		}
+
+		log.Debug("ProxyName [%s], write to client to add work conn success", server.Name)
+	}
+
+	return
+}
+
+func checkProxy(req *models.ClientCtlReq, c *conn.Conn) (succ bool, msg string, needRes bool) {
+	succ = false
+	needRes = true
+	// check if proxy name exist
+	server, ok := ProxyServers[req.ProxyName]
+	if !ok {
+		msg = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
+		log.Warn(msg)
+		return
+	}
+
+	// check password
+	if req.Passwd != server.Passwd {
+		msg = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName)
+		log.Warn(msg)
+		return
+	}
+	
+	// control conn
+	if req.Type == models.ControlConn {
+		if server.Status != models.Idle {
+			msg = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
+			log.Warn(msg)
+			return
+		}
+
+		// start proxy and listen for user conn, no block
+		err := server.Start()
+		if err != nil {
+			msg = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error())
+			log.Warn(msg)
+			return
+		}
+
+		log.Info("ProxyName [%s], start proxy success", req.ProxyName)
+	} else if req.Type == models.WorkConn {
+	// work conn
+		needRes = false
+		if server.Status != models.Working {
+			log.Warn("ProxyName [%s], is not working when it gets one new work conn", req.ProxyName)
+			return
+		}
+
+		server.CliConnChan <- c
+	} else {
+		msg = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName)
+		log.Warn(msg)
+		return
+	}
+
+	succ = true
+	return
+}

+ 26 - 0
cmd/frps/main.go

@@ -0,0 +1,26 @@
+package main
+
+import (
+	"os"
+
+	"frp/pkg/utils/log"
+	"frp/pkg/utils/conn"
+)
+
+func main() {
+	err := LoadConf("./frps.ini")
+	if err != nil {
+		os.Exit(-1)
+	}
+
+	log.InitLog(LogWay, LogFile, LogLevel)
+
+	l, err := conn.Listen(BindAddr, BindPort)
+	if err != nil {
+		log.Error("Create listener error, %v", err)
+		os.Exit(-1)
+	}
+
+	log.Info("Start frps success")
+	ProcessControlConn(l)
+}

+ 14 - 0
conf/frpc.ini

@@ -0,0 +1,14 @@
+# common是必须的section
+[common]
+server_addr = 127.0.0.1
+bind_port = 7000
+log_file = ./frpc.log
+# debug, info, warn, error
+log_level = info
+# file, console
+log_way = file
+
+# test1即为name
+[test1]
+passwd = 123
+local_port = 22

+ 15 - 0
conf/frps.ini

@@ -0,0 +1,15 @@
+# common是必须的section
+[common]
+bind_addr = 0.0.0.0
+bind_port = 7000
+log_file = ./frps.log
+# debug, info, warn, error
+log_level = info
+# file, console
+log_way = file
+
+# test1即为name
+[test1]
+passwd = 123
+bind_addr = 0.0.0.0
+listen_port = 6000

+ 70 - 0
pkg/models/client.go

@@ -0,0 +1,70 @@
+package models
+
+import (
+	"encoding/json"
+
+	"frp/pkg/utils/conn"
+	"frp/pkg/utils/log"
+)
+
+type ProxyClient struct {
+	Name		string
+	Passwd		string
+	LocalPort	int64
+}
+
+func (p *ProxyClient) GetLocalConn() (c *conn.Conn, err error) {
+	c = &conn.Conn{}
+	err = c.ConnectServer("127.0.0.1", p.LocalPort)
+	if err != nil {
+		log.Error("ProxyName [%s], connect to local port error, %v", p.Name, err)
+	}
+	return
+}
+
+func (p *ProxyClient) GetRemoteConn(addr string, port int64) (c *conn.Conn, err error) {
+	c = &conn.Conn{}
+	defer func(){
+		if err != nil {
+			c.Close()
+		}
+	}()
+
+	err = c.ConnectServer(addr, port)
+	if err != nil {
+		log.Error("ProxyName [%s], connect to server [%s:%d] error, %v", p.Name, addr, port, err)
+		return
+	}
+
+	req := &ClientCtlReq{
+		Type:		WorkConn,
+		ProxyName:	p.Name,
+		Passwd:		p.Passwd,
+	}
+
+	buf, _ := json.Marshal(req)
+	err = c.Write(string(buf) + "\n")
+	if err != nil {
+		log.Error("ProxyName [%s], write to server error, %v", p.Name, err)
+		return
+	}
+
+	err = nil
+	return
+}
+
+func (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) {
+	localConn, err := p.GetLocalConn()
+	if err != nil {
+		return
+	}
+	remoteConn, err := p.GetRemoteConn(serverAddr, serverPort)
+	if err != nil {
+		return
+	}
+
+	log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", localConn.GetLocalAddr(), localConn.GetRemoteAddr(),
+			remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr())
+	go conn.Join(localConn, remoteConn)
+	return nil
+}

+ 27 - 0
pkg/models/msg.go

@@ -0,0 +1,27 @@
+package models
+
+type GeneralRes struct {
+	Code			int64	`json:"code"`
+	Msg				string	`json:"msg"`
+}
+
+// type
+const (
+	ControlConn = iota
+	WorkConn
+)
+
+type ClientCtlReq struct {
+	Type			int64	`json:"type"`
+	ProxyName		string	`json:"proxy_name"`
+	Passwd			string	`json:"passwd"`
+}
+
+type ClientCtlRes struct {
+	GeneralRes
+}
+
+
+type ServerCtlReq struct {
+	Type			int64	`json:"type"`
+}

+ 116 - 0
pkg/models/server.go

@@ -0,0 +1,116 @@
+package models
+
+import (
+	"sync"
+	"container/list"
+
+	"frp/pkg/utils/conn"
+	"frp/pkg/utils/log"
+)
+
+const (
+	Idle = iota
+	Working
+)
+
+type ProxyServer struct {
+	Name			string
+	Passwd			string
+	BindAddr		string
+	ListenPort		int64
+
+	Status			int64
+	Listener		*conn.Listener		// accept new connection from remote users
+	CtlMsgChan		chan int64			// every time accept a new user conn, put "1" to the channel
+	CliConnChan		chan *conn.Conn		// get client conns from control goroutine
+	UserConnList	*list.List			// store user conns
+	Mutex			sync.Mutex
+}
+
+func (p *ProxyServer) Init() {
+	p.Status = Idle
+	p.CtlMsgChan = make(chan int64)
+	p.CliConnChan = make(chan *conn.Conn)
+	p.UserConnList = list.New()
+}
+
+func (p *ProxyServer) Lock() {
+	p.Mutex.Lock()
+}
+
+func (p *ProxyServer) Unlock() {
+	p.Mutex.Unlock()
+}
+
+// start listening for user conns
+func (p *ProxyServer) Start() (err error) {
+	p.Listener, err = conn.Listen(p.BindAddr, p.ListenPort)
+	if err != nil {
+		return err
+	}
+
+	p.Status = Working
+
+	// start a goroutine for listener
+	go func() {
+		for {
+			// block
+			c := p.Listener.GetConn()	
+			log.Debug("ProxyName [%s], get one new user conn [%s]", p.Name, c.GetRemoteAddr())
+
+			// put to list
+			p.Lock()
+			if p.Status != Working {
+				log.Debug("ProxyName [%s] is not working, new user conn close", p.Name)
+				c.Close()
+				p.Unlock()
+				return
+			}
+			p.UserConnList.PushBack(c)
+			p.Unlock()
+
+			// put msg to control conn
+			p.CtlMsgChan <- 1
+		}
+	}()
+
+	// start another goroutine for join two conns from client and user
+	go func() {
+		for {
+			cliConn := <-p.CliConnChan
+			p.Lock()
+			element := p.UserConnList.Front()
+
+			var userConn *conn.Conn
+			if element != nil {
+				userConn = element.Value.(*conn.Conn)
+				p.UserConnList.Remove(element)
+			} else {
+				cliConn.Close()
+				continue
+			}
+			p.Unlock()
+
+			// msg will transfer to another without modifying
+			log.Debug("Join two conns, (l[%s] r[%s]) (l[%s] r[%s])", cliConn.GetLocalAddr(), cliConn.GetRemoteAddr(),
+					userConn.GetLocalAddr(), userConn.GetRemoteAddr())
+			go conn.Join(cliConn, userConn)
+		}
+	}()
+
+	return nil
+}
+
+func (p *ProxyServer) Close() {
+	p.Lock()
+	p.Status = Idle
+	p.CtlMsgChan = make(chan int64)
+	p.CliConnChan = make(chan *conn.Conn)
+	p.UserConnList = list.New()
+	p.Unlock()
+}
+
+func (p *ProxyServer) WaitUserConn() (res int64) {
+	res = <-p.CtlMsgChan
+	return 
+}

+ 115 - 0
pkg/utils/conn/conn.go

@@ -0,0 +1,115 @@
+package conn
+
+import (
+	"fmt"
+	"net"
+	"bufio"
+	"sync"
+	"io"
+
+	"frp/pkg/utils/log"
+)
+
+type Listener struct {
+	Addr	net.Addr
+	Conns	chan *Conn
+}
+
+// wait util get one
+func (l *Listener) GetConn() (conn *Conn) {
+	conn = <-l.Conns
+	return conn
+}
+
+type Conn struct {
+	TcpConn		*net.TCPConn
+	Reader		*bufio.Reader
+}
+
+func (c *Conn) ConnectServer(host string, port int64) (err error) {
+	servertAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", host, port))
+	if err != nil {
+		return err
+	}
+	conn, err := net.DialTCP("tcp", nil, servertAddr)
+	if err != nil {
+		return err
+	}
+	c.TcpConn = conn
+	c.Reader = bufio.NewReader(c.TcpConn)
+	return nil
+}
+
+func (c *Conn) GetRemoteAddr() (addr string) {
+	return c.TcpConn.RemoteAddr().String()
+}
+
+func (c *Conn) GetLocalAddr() (addr string) {
+	return c.TcpConn.LocalAddr().String()
+}
+
+func (c *Conn) ReadLine() (buff string, err error) {
+	buff, err = c.Reader.ReadString('\n')
+	return buff, err
+}
+
+func (c *Conn) Write(content string) (err error) {
+	_, err = c.TcpConn.Write([]byte(content))
+	return err
+}
+
+func (c *Conn) Close() {
+	c.TcpConn.Close()
+}
+
+func Listen(bindAddr string, bindPort int64) (l *Listener, err error) {
+	tcpAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", bindAddr, bindPort))
+	listener, err := net.ListenTCP("tcp", tcpAddr)
+	if err != nil {
+		return l, err
+	}
+
+	l = &Listener{
+		Addr:	listener.Addr(),
+		Conns:	make(chan *Conn),
+	}
+
+	go func() {
+		for {
+			conn, err := listener.AcceptTCP()
+			if err != nil {
+				log.Error("Accept new tcp connection error, %v", err)
+				continue
+			}
+
+			c := &Conn{
+				TcpConn:	conn,
+			}
+			c.Reader = bufio.NewReader(c.TcpConn)
+			l.Conns <- c
+		}
+	}()
+	return l, err
+}
+
+// will block until conn close
+func Join(c1 *Conn, c2 *Conn) {
+	var wait sync.WaitGroup
+	pipe := func(to *Conn, from *Conn) {
+		defer to.Close()
+		defer from.Close()
+		defer wait.Done()
+
+		var err error
+		_, err = io.Copy(to.TcpConn, from.TcpConn)
+		if err != nil {
+			log.Warn("join conns error, %v", err)
+		}
+	}
+
+	wait.Add(2)
+	go pipe(c1, c2)
+	go pipe(c2, c1)
+	wait.Wait()
+	return
+}

+ 64 - 0
pkg/utils/log/log.go

@@ -0,0 +1,64 @@
+package log
+
+import (
+	"github.com/astaxie/beego/logs"
+)
+
+var Log *logs.BeeLogger
+
+func init() {
+	Log = logs.NewLogger(1000)
+	Log.EnableFuncCallDepth(true)
+	Log.SetLogFuncCallDepth(Log.GetLogFuncCallDepth() + 1)
+}
+
+func InitLog(logWay string, logFile string, logLevel string) {
+	SetLogFile(logWay, logFile)
+	SetLogLevel(logLevel)
+}
+
+// logWay: such as file or console
+func SetLogFile(logWay string, logFile string) {
+	if logWay == "console" {
+		Log.SetLogger("console", "")
+	} else {
+		Log.SetLogger("file", `{"filename": "` + logFile + `"}`)
+	}
+}
+
+// value: error, warning, info, debug
+func SetLogLevel(logLevel string) {
+	level := 4 // warning
+
+	switch logLevel {
+	case "error":
+		level = 3
+	case "warn":
+		level = 4
+	case "info":
+		level = 6
+	case "debug":
+		level = 7
+	default:
+		level = 4
+	}
+
+	Log.SetLevel(level)
+}
+
+// wrap log
+func Error(format string, v ...interface{}) {
+	Log.Error(format, v...)
+}
+
+func Warn(format string, v ...interface{}) {
+	Log.Warn(format, v...)
+}
+
+func Info(format string, v ...interface{}) {
+	Log.Info(format, v...)
+}
+
+func Debug(format string, v ...interface{}) {
+	Log.Debug(format, v...)
+}