├── http ├── cache │ └── rule.go ├── proxy │ ├── proxy.go │ └── client.go ├── fcgi │ ├── request.go │ ├── response.go │ ├── units.go │ ├── id.go │ ├── header.go │ ├── fcgi.go │ ├── buf.go │ └── protocol.go ├── error.go ├── response_test.go ├── socket.go ├── response.go ├── handler.go ├── protocol.go └── request.go ├── Wechat.jpeg ├── main.go ├── .gitignore ├── config └── server.json ├── LICENSE ├── client.go ├── conf └── config.go └── README.md /http/cache/rule.go: -------------------------------------------------------------------------------- 1 | package cache 2 | -------------------------------------------------------------------------------- /Wechat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwl1989/spinx/HEAD/Wechat.jpeg -------------------------------------------------------------------------------- /http/proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | type IProxy interface { 4 | Parse() 5 | Do() 6 | } 7 | 8 | type Proxy struct { 9 | 10 | } 11 | 12 | 13 | 14 | func Do(proxy IProxy) { 15 | //IProxy.Do() 16 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lwl1989/spinx/http" 5 | "github.com/lwl1989/spinx/conf" 6 | ) 7 | 8 | func main(){ 9 | conf.GetVhosts("config/server.json") 10 | http.Do() 11 | } 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /http/fcgi/request.go: -------------------------------------------------------------------------------- 1 | package fcgi 2 | 3 | import ( 4 | "bufio" 5 | "github.com/lwl1989/spinx/conf" 6 | ) 7 | 8 | type Request struct { 9 | Id uint16 10 | Rwc *bufio.Reader 11 | Host, Port string 12 | Header map[string]string //必要设置的Header 13 | KeepConn bool 14 | content []byte 15 | Method, RequestURI, Proto string 16 | Cf *conf.HostMap 17 | } 18 | -------------------------------------------------------------------------------- /http/fcgi/response.go: -------------------------------------------------------------------------------- 1 | package fcgi 2 | 3 | 4 | type ResponseContent struct { 5 | received chan bool 6 | err chan error 7 | buf []byte 8 | } 9 | 10 | func (res *ResponseContent) content() []byte { 11 | return res.buf 12 | } 13 | 14 | //Response Interface 15 | func (res *ResponseContent) String() string { 16 | return string(res.buf) 17 | } 18 | 19 | //Response Interface 20 | func (res *ResponseContent) Bytes() []byte { 21 | return res.buf 22 | } -------------------------------------------------------------------------------- /http/error.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "fmt" 4 | 5 | type ErrorMsg struct { 6 | code uint16 7 | title string 8 | value string 9 | } 10 | 11 | func GetError(code uint16, msg string) ErrorMsg { 12 | return ErrorMsg{ 13 | code:code, 14 | value:msg, 15 | } 16 | } 17 | 18 | 19 | func GetRenderHtml(code uint16) []byte { 20 | return make([]byte, 0) 21 | } 22 | 23 | func (msg ErrorMsg) Error() string { 24 | return fmt.Sprintf("HTTP/1.1 %d \r\n\r\n
%s
", msg.code, msg.code, msg.value) 25 | } 26 | -------------------------------------------------------------------------------- /http/response_test.go: -------------------------------------------------------------------------------- 1 | package http_test 2 | 3 | import ( 4 | "testing" 5 | corehttp "net/http" 6 | //"github.com/lwl1989/spinx/http" 7 | "fmt" 8 | "errors" 9 | lihttp "github.com/lwl1989/spinx/http" 10 | ) 11 | 12 | func TestResponse(t *testing.T) { 13 | res := &corehttp.Response{} 14 | res1 := &lihttp.Response{} 15 | maps := make(map[string]interface{}) 16 | maps["http"] = res 17 | maps["http_li"] = res1 18 | maps["error"] = errors.New("errors") 19 | 20 | for k,r1 := range maps { 21 | switch r1.(type) { 22 | case error: 23 | fmt.Println(k,r1) 24 | break 25 | case *corehttp.Response: 26 | fmt.Println(k,r1) 27 | break 28 | case *lihttp.Response: 29 | fmt.Println(k,r1) 30 | break 31 | default: 32 | fmt.Println(k,r1) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /http/fcgi/units.go: -------------------------------------------------------------------------------- 1 | package fcgi 2 | 3 | import "encoding/binary" 4 | 5 | //func readSize(s []byte) (uint32, int) { 6 | // if len(s) == 0 { 7 | // return 0, 0 8 | // } 9 | // size, n := uint32(s[0]), 1 10 | // if size&(1<<7) != 0 { 11 | // if len(s) < 4 { 12 | // return 0, 0 13 | // } 14 | // n = 4 15 | // size = binary.BigEndian.Uint32(s) 16 | // size &^= 1 << 31 17 | // } 18 | // return size, n 19 | //} 20 | 21 | //func readString(s []byte, size uint32) string { 22 | // if size > uint32(len(s)) { 23 | // return "" 24 | // } 25 | // return string(s[:size]) 26 | //} 27 | 28 | func encodeSize(b []byte, size uint32) int { 29 | if size > 127 { 30 | size |= 1 << 31 31 | binary.BigEndian.PutUint32(b, size) 32 | return 4 33 | } 34 | b[0] = byte(size) 35 | return 1 36 | } 37 | -------------------------------------------------------------------------------- /http/socket.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net" 5 | "time" 6 | "log" 7 | "github.com/lwl1989/spinx/conf" 8 | ) 9 | 10 | 11 | func Do() { 12 | multiDo() 13 | normalDo() 14 | } 15 | 16 | func normalDo() { 17 | listen("8888") 18 | } 19 | 20 | func multiDo() { 21 | ports := conf.HostMaps.GetPorts() 22 | 23 | for _,port := range ports { 24 | go listen(port) 25 | } 26 | } 27 | 28 | func listen(port string) { 29 | l, err := net.Listen("tcp", ":"+port) 30 | if err != nil { 31 | log.Println("error listen:", err) 32 | return 33 | } 34 | defer l.Close() 35 | //log.Println("listen ok") 36 | 37 | 38 | 39 | for { 40 | Conn, err := l.Accept() 41 | //time.Sleep(time.Second * 10) 42 | if err != nil { 43 | log.Println("accept error:", err) 44 | time.Sleep(time.Second * 10) 45 | continue 46 | } 47 | go Handler(Conn) 48 | } 49 | } -------------------------------------------------------------------------------- /config/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "port": "8081", 4 | "log": "/tmp/spinx.log", 5 | "keep_alive_timeout": 3, 6 | "gzip_level": 1, 7 | "cache": { 8 | "len": 10240, 9 | "expire": "24h" 10 | } 11 | }, 12 | 13 | "vhosts": [ 14 | { 15 | "name": "www.test.com wwww.aaa.com", 16 | "port": "18000", 17 | "proxy":"127.0.0.1:9000", 18 | "documentRoot": "/www/web/wordpress", 19 | "tryFiles": "/index.php?$uri", 20 | "index": "index.php index.html" 21 | }, 22 | { 23 | "name": "www.phpmyadmin.com", 24 | "port": "18000", 25 | "proxy":"127.0.0.1:9000", 26 | "documentRoot": "/www/web/phpmyadmin", 27 | "tryFiles": "/index.php?$uri" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /http/fcgi/id.go: -------------------------------------------------------------------------------- 1 | package fcgi 2 | 3 | import "sync" 4 | 5 | 6 | // Request hold information of a standard 7 | // FastCGI request 8 | type IdPool struct { 9 | IDs chan uint16 10 | } 11 | 12 | // AllocID implements Client.AllocID 13 | func (p *IdPool) Alloc() uint16 { 14 | return <-p.IDs 15 | } 16 | 17 | // ReleaseID implements Client.ReleaseID 18 | func (p *IdPool) Release(id uint16) { 19 | go func() { 20 | // release the ID back to channel for reuse 21 | // use goroutine to prev0, ent blocking ReleaseID 22 | p.IDs <- id 23 | }() 24 | } 25 | 26 | var pool *IdPool 27 | var one sync.Once 28 | func GetIdPool(limit uint16) *IdPool { 29 | one.Do(func(){ 30 | var ids = make(chan uint16) 31 | 32 | go func(maxID uint16) { 33 | for i := uint16(0); i < maxID; i++ { 34 | ids <- i 35 | } 36 | ids <- uint16(maxID) 37 | }(uint16(limit - 1)) 38 | pool = &IdPool{IDs:ids} 39 | }) 40 | return pool 41 | } -------------------------------------------------------------------------------- /http/response.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type IResponse interface { 8 | String() string 9 | Bytes() []byte 10 | } 11 | 12 | // http response interface 13 | // getter setter content 14 | // getter setter code 15 | // String and bytes contents 16 | type IHttpResponse interface { 17 | String() string 18 | Bytes() []byte 19 | SetCode(code uint) 20 | GetCode() uint 21 | SetContent(content []byte) 22 | GetContent() []byte 23 | } 24 | 25 | type Response struct { 26 | content []byte 27 | } 28 | 29 | func (response *Response) Bytes() []byte { 30 | return nil 31 | } 32 | 33 | func (response *Response) String() string { 34 | return "" 35 | } 36 | 37 | func DoResponse(conn net.Conn, buf []byte) { 38 | conn.Write(buf) 39 | } 40 | 41 | func Error(conn net.Conn, err error) { 42 | DoResponse(conn, []byte(err.Error())) 43 | } 44 | 45 | func Success(conn net.Conn, response IResponse) { 46 | DoResponse(conn, response.Bytes()) 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 李文龍(Mars) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /http/handler.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "net" 6 | "net/http" 7 | "errors" 8 | "reflect" 9 | ) 10 | 11 | //accept user request with socket 12 | //handler this request and parse http protocol text 13 | //build context obj do any thing 14 | func Handler(conn net.Conn) { 15 | req := &Request{ 16 | KeepConn:false, 17 | Rwc: bufio.NewReader(conn), 18 | } 19 | 20 | cf,err := req.Parse() 21 | if err != nil { 22 | Error(conn, err) 23 | return 24 | } 25 | 26 | ctx := &Context{ 27 | Cf:cf, 28 | req:req, 29 | res:make(chan interface{}), 30 | err:make(chan error), 31 | } 32 | ctx.Do() 33 | 34 | for{ 35 | select { 36 | case res := <-ctx.res: 37 | switch res.(type) { 38 | case error: 39 | Error(conn, err) 40 | break 41 | case *http.Response: 42 | rs := res.(*http.Response) 43 | rs.Write(conn) 44 | break 45 | case *Response: 46 | Success(conn, res.(*Response)) 47 | break 48 | default: 49 | Error(conn, errors.New("not support with type "+reflect.TypeOf(res).String())) 50 | } 51 | case err := <-ctx.err: 52 | Error(conn, err) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /http/proxy/client.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "github.com/lwl1989/spinx/conf" 5 | "net/http" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | type ClientProxy struct { 11 | Req *http.Request 12 | Res *http.Response 13 | } 14 | 15 | type Request struct { 16 | Rwc io.Reader 17 | Header map[string]string //必要设置的Header 18 | KeepConn bool 19 | Host, Port, Method, RequestURI, Proto string 20 | Cf *conf.HostMap 21 | } 22 | 23 | func New(req *Request) (client *ClientProxy, err error) { 24 | 25 | reqHttp,err := http.NewRequest(req.Method, req.Cf.Proxy+req.RequestURI, req.Rwc) 26 | reqHttp.Header = parseHeader(req.Header) 27 | 28 | client = &ClientProxy{ 29 | Req:reqHttp, 30 | } 31 | return client,nil 32 | } 33 | 34 | func parseHeader(oldHeader map[string]string) http.Header { 35 | header := make(http.Header) 36 | for k,v := range oldHeader { 37 | header[k] = make([]string,0) 38 | if strings.Index(v, ";") != -1 { 39 | split := strings.Split(v, ";") 40 | for _,value := range split { 41 | header[k] = append(header[k], value) 42 | } 43 | }else{ 44 | header[k] = append(header[k], v) 45 | } 46 | } 47 | 48 | return header 49 | } 50 | 51 | func (client *ClientProxy) DoRequest() (err error) { 52 | c := &http.Client{} 53 | client.Res,err = c.Do(client.Req) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | return err 59 | } -------------------------------------------------------------------------------- /http/fcgi/header.go: -------------------------------------------------------------------------------- 1 | package fcgi 2 | 3 | import ( 4 | "io" 5 | "encoding/binary" 6 | "errors" 7 | ) 8 | 9 | const ( 10 | maxWrite = 65535 // maximum record body 11 | maxPad = 255 12 | ) 13 | 14 | //It's fcgi header 15 | type header struct { 16 | Version uint8 17 | Type uint8 18 | Id uint16 19 | ContentLength uint16 20 | PaddingLength uint8 21 | Reserved uint8 22 | } 23 | 24 | // for padding so we don't have to allocate all the time 25 | // not synchronized because we don't care what the contents are 26 | var pad [maxPad]byte 27 | 28 | func (h *header) init(recType uint8, reqID uint16, contentLength int) { 29 | h.Version = 1 30 | h.Type = recType 31 | h.Id = reqID 32 | h.ContentLength = uint16(contentLength) 33 | h.PaddingLength = uint8(-contentLength & 7) 34 | } 35 | 36 | type record struct { 37 | h header 38 | buf [maxWrite + maxPad]byte 39 | received chan bool 40 | err chan error 41 | } 42 | 43 | func (rec *record) read(r io.Reader) (err error) { 44 | if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { 45 | ///fmt.Println("binary error"+err.Error()) 46 | return err 47 | } 48 | //fmt.Println(rec.h) 49 | if rec.h.Version != 1 { 50 | return errors.New("fcgi: invalid header version") 51 | } 52 | n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) 53 | if _, err = io.ReadFull(r, rec.buf[:n]); err != nil { 54 | //fmt.Println("full error"+err.Error()) 55 | return err 56 | } 57 | return nil 58 | } 59 | 60 | func (rec *record) content() []byte { 61 | return rec.buf[:rec.h.ContentLength] 62 | } 63 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | ) 8 | 9 | func sender(conn net.Conn) { 10 | words := "中哈发生的鬼地方施工图第三个范德萨干豆腐花打工皇帝繁琐嘛;" + 11 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 12 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 13 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 14 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 15 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 16 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 17 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 18 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 19 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 20 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 21 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 22 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 23 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 24 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 25 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 26 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 27 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 28 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 29 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 30 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上"+ 31 | "国贸大厦;方面的是;方面对方;还没答复;厉害吗;地方了还没顾得上;了国民党是;老干妈代理商;方" + 32 | "面sad;浪费马拉松;富马酸了;房东马上老;富马酸;浪费马上啊啊啊啊啊" 33 | b := []byte(words) 34 | conn.Write(b) 35 | fmt.Println("send over",len(b)) 36 | 37 | } 38 | 39 | 40 | 41 | func main() { 42 | server := "127.0.0.1:8888" 43 | tcpAddr, err := net.ResolveTCPAddr("tcp4", server) 44 | if err != nil { 45 | fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) 46 | os.Exit(1) 47 | } 48 | 49 | conn, err := net.DialTCP("tcp", nil, tcpAddr) 50 | if err != nil { 51 | fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) 52 | os.Exit(1) 53 | } 54 | 55 | fmt.Println("connect success") 56 | sender(conn) 57 | 58 | } -------------------------------------------------------------------------------- /http/fcgi/fcgi.go: -------------------------------------------------------------------------------- 1 | package fcgi 2 | 3 | //主要用于解析http 4 | //并且将协议拆解成N份 主要是:头(一般头,自定义头, fast cgi协议头) 主体 5 | //cookies不需要解析 原包不动即可 6 | //通过解析结果获取到请求的配置 7 | //结果不需要拆解 直接发送 提高性能 8 | 9 | func buildEnv(req *Request) (err error, env map[string]string) { 10 | env = make(map[string]string) 11 | index := "index.php" 12 | //todo:ceshi 13 | req.Cf.DocumentRoot = "/Users/wenglong11/PhpstormProjects/yaf/public" 14 | filename := req.Cf.DocumentRoot + "/" + index 15 | 16 | 17 | //for name, value := range req.cf.serverEnvironment { 18 | // env[name] = value 19 | //} 20 | 21 | 22 | env["DOCUMENT_ROOT"] = req.Cf.DocumentRoot 23 | env["SCRIPT_FILENAME"] = filename 24 | env["SCRIPT_NAME"] = "/" + index 25 | 26 | env["REQUEST_SCHEME"] = "http" 27 | env["SERVER_NAME"] = "" 28 | env["SERVER_PORT"] = req.Port 29 | env["REDIRECT_STATUS"] = "200" 30 | env["HTTP_HOST"] = req.Host 31 | env["SERVER_SOFTWARE"] = "spinx/1.0.0" 32 | //env["REMOTE_ADDR"] = "127.0.0.1" 33 | env["SERVER_PROTOCOL"] = "HTTP/1.1" 34 | env["GATEWAY_INTERFACE"] = "CGI/1.1" 35 | 36 | env["PATH_INFO"] = "" 37 | //if r.URL.Path != "/" { 38 | // env["PATH_INFO"] = r.URL.Path 39 | //} 40 | env["REQUEST_METHOD"] = req.Method 41 | env["REQUEST_URI"] = "/" 42 | env["QUERY_STRING"] = "" 43 | if env["QUERY_STRING"] != "" { 44 | env["REQUEST_URI"] = env["REQUEST_URI"] + "?" + env["QUERY_STRING"] 45 | } else { 46 | env["REQUEST_URI"] = env["REQUEST_URI"] 47 | } 48 | 49 | env["DOCUMENT_URI"] = env["SCRIPT_NAME"] 50 | env["PHP_SELF"] = env["SCRIPT_NAME"] 51 | 52 | 53 | //只有自定义的需要加上HTTP_xxx_XXX_XXX 54 | //for header, values := range r.Header { 55 | // env["HTTP_"+strings.Replace(strings.ToUpper(header), "-", "_", -1)] = values[0] 56 | //} 57 | 58 | env["CONTENT_LENGTH"] = "0" 59 | env["CONTENT_TYPE"] = "text/html" 60 | //log.Println() 61 | return nil, env 62 | } -------------------------------------------------------------------------------- /http/protocol.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "github.com/lwl1989/spinx/conf" 6 | "github.com/lwl1989/spinx/http/fcgi" 7 | "github.com/lwl1989/spinx/http/proxy" 8 | ) 9 | 10 | //context 11 | //has conf 12 | //build req 13 | //received chan res or chan error 14 | //do Response 15 | type Context struct { 16 | Cf *conf.HostMap 17 | req *Request 18 | res chan interface{} 19 | err chan error 20 | } 21 | 22 | // do ctxcotol 23 | // check config and get how to do 24 | // ctxxy ? cache ? fastcgi ? 25 | // 26 | func (ctx *Context) Do() { 27 | if ctx.Cf.Proxy != "" { 28 | ctx.DoProxy() 29 | } 30 | 31 | if ctx.Cf.CacheRule != "" { 32 | ctx.DoCache() 33 | } 34 | 35 | if ctx.Cf.CgiProxy != "" { 36 | ctx.DoCgi() 37 | }else { 38 | ctx.err <- errors.New("can't do this ctxcotol") 39 | } 40 | } 41 | 42 | // cache 43 | func (ctx *Context) DoCache() { 44 | 45 | } 46 | // read config and ctxxy 47 | func (ctx *Context) DoProxy() { 48 | req := &proxy.Request{ 49 | Cf:ctx.Cf, 50 | Rwc: ctx.req.Rwc, 51 | Method: ctx.req.Method, 52 | Host: ctx.req.Host, 53 | Port: ctx.req.Port, 54 | Header: make(map[string]string), 55 | KeepConn:ctx.req.KeepConn, 56 | RequestURI:ctx.req.RequestURI, 57 | Proto:ctx.req.Proto, 58 | } 59 | xy,err := proxy.New(req) 60 | if err != nil { 61 | ctx.err <- err 62 | return 63 | } 64 | err = xy.DoRequest() 65 | if err != nil { 66 | ctx.err <- err 67 | return 68 | } 69 | ctx.res <- xy.Res 70 | } 71 | 72 | // read config and build cgi ctxtocol 73 | func (ctx *Context) DoCgi() { 74 | req := &fcgi.Request{ 75 | Cf:ctx.Cf, 76 | Rwc: ctx.req.Rwc, 77 | Method: ctx.req.Method, 78 | Host: ctx.req.Host, 79 | Port: ctx.req.Port, 80 | Header: make(map[string]string), 81 | KeepConn:ctx.req.KeepConn, 82 | RequestURI:ctx.req.RequestURI, 83 | Proto:ctx.req.Proto, 84 | } 85 | cgi,err := fcgi.New(req) 86 | if err != nil { 87 | ctx.err <- err 88 | return 89 | } 90 | 91 | content, err := cgi.DoRequest() 92 | if err != nil { 93 | ctx.err <- err 94 | return 95 | } 96 | ctx.res <- &Response{ 97 | content: content, 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /http/fcgi/buf.go: -------------------------------------------------------------------------------- 1 | package fcgi 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "bufio" 7 | ) 8 | 9 | const enter byte = 13 10 | const line byte = 10 11 | 12 | 13 | 14 | func Read(reader io.ReadCloser) []byte { 15 | buffer := make([]byte, 0) 16 | for { 17 | buf := make([]byte, 1024) 18 | n, err := reader.Read(buf) 19 | if n < 1024 { 20 | buf = buf[0:n] 21 | } 22 | if err != nil { 23 | if err == io.EOF { 24 | log.Println("Eof", len(buffer), n) 25 | return buffer 26 | } 27 | } 28 | if n < 1 { 29 | log.Println("read over") 30 | return buffer 31 | } 32 | buffer = append(buffer, buf[:]...) 33 | } 34 | 35 | return buffer 36 | } 37 | 38 | 39 | 40 | type bufWriter struct { 41 | closer io.Closer 42 | *bufio.Writer 43 | } 44 | 45 | // close BuffWrite impl io.Closer 46 | func (b *bufWriter) Close() error { 47 | if err := b.Writer.Flush(); err != nil { 48 | b.closer.Close() 49 | return err 50 | } 51 | return b.closer.Close() 52 | } 53 | 54 | //get a new buf 55 | func newWriter(c *CgiClient, recType uint8, reqId uint16) *bufWriter { 56 | s := &streamWriter{c: c, recType: recType, reqId: reqId} 57 | return &bufWriter{s, bufio.NewWriterSize(s, maxWrite)} 58 | } 59 | 60 | // streamWriter abstracts out the separation of a stream into discrete records. 61 | // It only writes maxWrite bytes at a time. 62 | type streamWriter struct { 63 | c *CgiClient 64 | recType uint8 65 | reqId uint16 66 | } 67 | 68 | //write stream impl io.Writer 69 | func (w *streamWriter) Write(p []byte) (int, error) { 70 | nn := 0 71 | for len(p) > 0 { 72 | n := len(p) 73 | if n > maxWrite { 74 | n = maxWrite 75 | } 76 | 77 | if w.recType == 5 { 78 | //fmt.Println(w.recType, w.reqId, p[:]) 79 | //str := "dbname=wordpress&uname=username&pwd=password&dbhost=localhost&prefix=wp_&language=&submit=Submit" 80 | 81 | if err := w.c.writeRecord(w.recType, w.reqId, p); err != nil { 82 | return nn, err 83 | } 84 | } else { 85 | if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil { 86 | return nn, err 87 | } 88 | } 89 | 90 | nn += n 91 | p = p[n:] 92 | } 93 | return nn, nil 94 | } 95 | //write stream impl io.Closer 96 | func (w *streamWriter) Close() error { 97 | // send empty record to close the stream 98 | return w.c.writeRecord(w.recType, w.reqId, nil) 99 | } -------------------------------------------------------------------------------- /http/request.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bufio" 5 | "github.com/lwl1989/spinx/conf" 6 | "strings" 7 | ) 8 | 9 | const enter byte = 13 10 | const line byte = 10 11 | //用于获取头的位置 12 | const SPLIT_STR = "\n\n" 13 | //用户获取特定头 14 | const ENTER_SPACE = "\n" 15 | //请求头部字符串 16 | const REQUEST_URL = "Request URL:" 17 | 18 | 19 | type Request struct { 20 | Id uint16 21 | Rwc *bufio.Reader 22 | KeepConn bool 23 | Host, Port, Method, RequestURI, Proto string 24 | } 25 | 26 | 27 | 28 | //执行此方法 获取 Request URL: 29 | //获取解析到HOST PORT 获得CGI转发的配置 30 | //并且获取到双换行的位置 31 | //然后通过配置获取到的参数 修改头 协议需要的 增加头 32 | //document_file index等等 33 | func (req *Request) Parse() (cf *conf.HostMap, e error) { 34 | l, _, err := req.Rwc.ReadLine() 35 | if err != nil { 36 | return nil,GetError(500, err.Error()) 37 | } 38 | 39 | var ok bool 40 | req.Method, req.RequestURI, req.Proto, ok = ParseRequestLine(string(l[:])) 41 | req.Method = strings.ToUpper(req.Method) 42 | if !ok { 43 | return nil,GetError(500, err.Error()) 44 | } 45 | //fmt.Println(Method, prouestURI, Proto, ok) 46 | 47 | //Host: localhost:8888 48 | l, _, err = req.Rwc.ReadLine() 49 | if err != nil { 50 | return nil,GetError(404, err.Error()) 51 | } 52 | 53 | req.Host, req.Port, ok = ParseHostLine(string(l[:])) 54 | if !ok { 55 | return nil,GetError(404, err.Error()) 56 | } 57 | 58 | 59 | cf,err = conf.HostMaps.GetHostMap(req.Port, req.Host) 60 | if err != nil { 61 | return nil,GetError(404, err.Error()) 62 | } 63 | 64 | //根据配置是走proxy还是走fcgi还是走cache 65 | return cf,nil 66 | } 67 | 68 | 69 | // parseRequestLine parses "GET /foo HTTP/1.1" into its three parts. 70 | func ParseRequestLine(line string) (method, requestURI, proto string, ok bool) { 71 | s1 := strings.Index(line, " ") 72 | s2 := strings.Index(line[s1+1:], " ") 73 | if s1 < 0 || s2 < 0 { 74 | return 75 | } 76 | s2 += s1 + 1 77 | return line[:s1], line[s1+1 : s2], line[s2+1:], true 78 | } 79 | 80 | //parse host line "HOST: localhost:8000" 81 | func ParseHostLine(line string) (host, port string, ok bool) { 82 | port = "80" 83 | s1 := strings.Index(line, " ") 84 | if s1 < 0 { 85 | return 86 | } 87 | s2 := strings.Index(line[s1+1:], ":") 88 | 89 | if s2 < 0 { 90 | host = line[s1+1:] 91 | return host,port,true 92 | } 93 | return line[s1+1:s2+s1+1],line[s2+s1+2:],true 94 | } 95 | -------------------------------------------------------------------------------- /conf/config.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "github.com/jingweno/conf" 5 | "fmt" 6 | "log" 7 | "strings" 8 | "reflect" 9 | "errors" 10 | ) 11 | var HostMaps Vhosts 12 | 13 | type HostMap struct{ 14 | Name string 15 | Net string 16 | Addr string 17 | TryFiles string 18 | DocumentRoot string 19 | Index string 20 | CgiProxy string //cgi 21 | Proxy string //proxy 22 | CacheRule string //rule 23 | } 24 | 25 | type Vhosts map[string]map[string]*HostMap 26 | 27 | 28 | func GetVhosts(path string) Vhosts { 29 | 30 | c, err := conf.NewLoader().Argv().Env().File(path).Load() 31 | 32 | if err != nil { 33 | fmt.Println(err) 34 | log.Fatal("lode config err") 35 | return nil 36 | } 37 | 38 | config := c.Get("vhosts") 39 | var hMap = make(Vhosts) 40 | for _,v := range config.([]interface{}) { 41 | keyValue := v.(map[string]interface{}) 42 | port := keyValue["port"].(string) 43 | proxy := getValue(keyValue,"proxy") 44 | isIp := strings.Contains(getValue(keyValue,"proxy"),".") 45 | net,addr := "tcp","" 46 | if !isIp { 47 | net = "unix" 48 | } 49 | addr = proxy 50 | names := strings.Split(getValue(keyValue,"name")," ") 51 | 52 | 53 | if _,ok := hMap[port]; !ok { 54 | hMap[port] = make(map[string]*HostMap) 55 | for _, name := range names { 56 | hMap[port][name] = &HostMap{ 57 | Name: name, 58 | Net: net, 59 | Addr: addr, 60 | TryFiles: getValue(keyValue, "tryFiles"), 61 | DocumentRoot: getValue(keyValue, "documentRoot"), 62 | Index: getValue(keyValue, "index"), 63 | } 64 | } 65 | }else{ 66 | for _, name := range names { 67 | hMap[port][name] = &HostMap{ 68 | Name: name, 69 | Net: net, 70 | Addr: addr, 71 | TryFiles: getValue(keyValue, "tryFiles"), 72 | DocumentRoot: getValue(keyValue, "documentRoot"), 73 | } 74 | } 75 | } 76 | } 77 | HostMaps = hMap 78 | return hMap 79 | } 80 | 81 | func (vhosts Vhosts) GetPorts() []string { 82 | var hosts = make([]string,0) 83 | for k,_ := range vhosts { 84 | hosts = append(hosts, k) 85 | } 86 | return hosts 87 | } 88 | 89 | func (vhosts Vhosts) GetNames(port string) []string { 90 | var names = make([]string,0) 91 | for k,_ := range vhosts[port] { 92 | names = append(names, k) 93 | } 94 | return names 95 | } 96 | 97 | func (vhosts Vhosts) GetHostMap(port,host string) (*HostMap,error) { 98 | if _,ok := vhosts[port][host]; !ok { 99 | return &HostMap{},errors.New("config not found") 100 | } 101 | return vhosts[port][host],nil 102 | } 103 | 104 | func (vhosts Vhosts) Get(port,host,key string) string { 105 | hMap := vhosts[port][host] 106 | object := reflect.ValueOf(hMap) 107 | for i:=0; i"+content+"
").Bytes()) 200 | } 201 | 202 | //if is proxy request 203 | //do request and get response 204 | func (cgi *CgiClient) DoRequest() (retout []byte, err error) { 205 | pool := GetIdPool(65535) 206 | reqId := pool.Alloc() 207 | //close connection and release id 208 | defer func() { 209 | pool.Release(reqId) 210 | cgi.writeEndRequest(reqId, 200, 0) 211 | pool.Release(reqId) 212 | cgi.rwc.Close() 213 | }() 214 | 215 | cgi.request.Id = reqId 216 | if cgi.request.KeepConn { 217 | //if it's keep-alive 218 | //set flags 1 219 | err = cgi.writeBeginRequest(reqId, roleResponder, 1) 220 | } else { 221 | err = cgi.writeBeginRequest(reqId, roleResponder, 0) 222 | } 223 | 224 | if err != nil { 225 | return nil, err 226 | } 227 | 228 | err = cgi.writeHeader(typeParams, reqId, cgi.request) 229 | if err != nil { 230 | return nil, err 231 | } 232 | //todo: 这个时间应该从配置中读取 233 | timer := time.NewTimer(1*time.Second) 234 | 235 | if cgi.request.Method != "GET" && cgi.request.Method != "HEAD" { 236 | err = cgi.writeBody(typeStdin, reqId, cgi.request) 237 | if err != nil { 238 | return nil, err 239 | } 240 | } 241 | 242 | res := &ResponseContent{ 243 | received:make(chan bool), 244 | err:make(chan error), 245 | buf:make([]byte,0), 246 | } 247 | go readResponse(cgi, res) 248 | // recive untill EOF or FCGI_END_REQUEST 249 | // todo :if time out add Connection: close 250 | for { 251 | select { 252 | case <- timer.C: 253 | //超时发送终止请求 254 | cgi.writeEndRequest(reqId, 502, 1) 255 | err = errors.New("502 timeout") 256 | return retout,err 257 | case <-res.received: 258 | retout = res.content() 259 | //fmt.Println(string(retout[:])+" has received") 260 | //fmt.Println(string(res.buf[:])) 261 | return retout,err 262 | case e:= <-res.err: 263 | err = e 264 | return retout,err 265 | } 266 | } 267 | 268 | return retout,err 269 | } 270 | 271 | func readResponse(cgi *CgiClient,res *ResponseContent) { 272 | //bb := Read(cgi.rwc) 273 | //fmt.Println(string(bb[:])) 274 | for { 275 | rec := &record{} 276 | err1 := rec.read(cgi.rwc) 277 | //if !keep-alive the end has EOF 278 | if err1 != nil { 279 | if err1 != io.EOF { 280 | res.err <- err1 281 | } else { 282 | res.received <- true 283 | } 284 | break 285 | } 286 | //fmt.Println(rec.h.Type) 287 | switch { 288 | case rec.h.Type == typeStdout: 289 | res.buf = append(res.buf, rec.content()...) 290 | //fmt.Println(string(rec.buf[:])) 291 | case rec.h.Type == typeStderr: 292 | //fmt.Println(string(rec.buf[:])) 293 | res.buf = append(res.buf, rec.content()...) 294 | case rec.h.Type == typeEndRequest: 295 | //if keep-alive 296 | //It's had return 297 | //But connection Not close 298 | //fmt.Println("end") 299 | //fmt.Println(string(rec.buf[:])) 300 | res.buf = append(res.content(), rec.content()...) 301 | res.received <- true 302 | return 303 | default: 304 | //fallthrough 305 | } 306 | } 307 | } 308 | --------------------------------------------------------------------------------