func (u *Upstream) FilterRequest(request *falcore.Request) (res *http.Response) { var err error req := request.HttpRequest // Force the upstream to use http if u.ForceHttp || req.URL.Scheme == "" { req.URL.Scheme = "http" req.URL.Host = req.Host } before := time.Now() req.Header.Set("Connection", "Keep-Alive") if u.tcpconn != nil { u.tcpconn.SetDeadline(time.Now().Add(u.Timeout)) } res, err = u.transport.RoundTrip(req) diff := falcore.TimeDiff(before, time.Now()) if err != nil { if nerr, ok := err.(net.Error); ok && nerr.Timeout() { falcore.Error("%s Upstream Timeout error: %v", request.ID, err) res = falcore.SimpleResponse(req, 504, nil, "Gateway Timeout\n") request.CurrentStage.Status = 2 // Fail } else { falcore.Error("%s Upstream error: %v", request.ID, err) res = falcore.SimpleResponse(req, 502, nil, "Bad Gateway\n") request.CurrentStage.Status = 2 // Fail } } falcore.Debug("%s [%s] [%s] %s s=%d Time=%.4f", request.ID, req.Method, u.host, req.URL, res.StatusCode, diff) return }
func (u *Upstream) ping() (up bool, ok bool) { if u.PingPath != "" { // the url must be syntactically valid for this to work but the host will be ignored because we // are overriding the connection always request, err := http.NewRequest("GET", "http://localhost"+u.PingPath, nil) request.Header.Set("Connection", "Keep-Alive") // not sure if this should be here for a ping if err != nil { falcore.Error("Bad Ping request: %v", err) return false, true } if u.tcpconn != nil { u.tcpconn.SetDeadline(time.Now().Add(u.Timeout)) } res, err := u.transport.RoundTrip(request) if err != nil { falcore.Error("Failed Ping to %v:%v: %v", u.Host, u.Port, err) return false, true } else { res.Body.Close() } if res.StatusCode == 200 { return true, true } falcore.Error("Failed Ping to %v:%v: %v", u.Host, u.Port, res.Status) // bad status return false, true } return false, false }
// The config consists of a map of the servers in the pool in the format host_or_ip:port // where port is optional and defaults to 80. The map value is an int with the weight // only 0 and 1 are supported weights (0 disables a server and 1 enables it) func NewUpstreamPool(name string, config []UpstreamEntryConfig) *UpstreamPool { up := new(UpstreamPool) up.pool = make([]*UpstreamEntry, len(config)) up.Name = name up.nextUpstream = make(chan *UpstreamEntry) up.weightMutex = new(sync.RWMutex) up.shutdown = make(chan int) up.pinger = time.NewTicker(3e9) // 3s // create the pool for i, uec := range config { parts := strings.Split(uec.HostPort, ":") upstreamHost := parts[0] upstreamPort := 80 if len(parts) > 1 { var err error upstreamPort, err = strconv.Atoi(parts[1]) if err != nil { upstreamPort = 80 falcore.Error("UpstreamPool Error converting port to int for", upstreamHost, ":", err) } } ups := NewUpstream(upstreamHost, upstreamPort, uec.ForceHttp) ups.PingPath = uec.PingPath ue := new(UpstreamEntry) ue.Upstream = ups ue.Weight = uec.Weight up.pool[i] = ue } go up.nextServer() go up.pingUpstreams() return up }
func (f *Filter) FilterRequest(req *falcore.Request) (res *http.Response) { // Clean asset path asset_path := filepath.Clean(filepath.FromSlash(req.HttpRequest.URL.Path)) // Resolve PathPrefix if strings.HasPrefix(asset_path, f.PathPrefix) { asset_path = asset_path[len(f.PathPrefix):] } else { falcore.Debug("%v doesn't match prefix %v", asset_path, f.PathPrefix) res = falcore.SimpleResponse(req.HttpRequest, 404, nil, "Not found.") return } // Resolve FSBase if f.BasePath != "" { asset_path = filepath.Join(f.BasePath, asset_path) } else { falcore.Error("file_filter requires a BasePath") return falcore.SimpleResponse(req.HttpRequest, 500, nil, "Server Error\n") } // Open File if file, err := os.Open(asset_path); err == nil { // Make sure it's an actual file if stat, err := file.Stat(); err == nil && stat.Mode()&os.ModeType == 0 { res = &http.Response{ Request: req.HttpRequest, StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Body: file, Header: make(http.Header), ContentLength: stat.Size(), } if ct := mime.TypeByExtension(filepath.Ext(asset_path)); ct != "" { res.Header.Set("Content-Type", ct) } } else { file.Close() return falcore.SimpleResponse(req.HttpRequest, 404, nil, "File not found\n") } } else { falcore.Debug("Can't open %v: %v", asset_path, err) res = falcore.SimpleResponse(req.HttpRequest, 404, nil, "File not found\n") } return }
func NewUpstream(host string, port int, forceHttp bool, rewriteHost bool) *Upstream { u := new(Upstream) u.Host = host u.Port = port u.ForceHttp = forceHttp u.RewriteHost = rewriteHost ips, err := net.LookupIP(host) var ip net.IP = nil for i := range ips { ip = ips[i].To4() if ip != nil { break } } if err == nil && ip != nil { u.tcpaddr = new(net.TCPAddr) u.tcpaddr.Port = port u.tcpaddr.IP = ip } else { falcore.Warn("Can't get IP addr for %v: %v", host, err) } u.Timeout = 60e9 u.host = fmt.Sprintf("%v:%v", u.Host, u.Port) u.transport = new(http.Transport) u.transport.Dial = func(n, addr string) (c net.Conn, err error) { falcore.Fine("Dialing connection to %v", u.tcpaddr) var ctcp *net.TCPConn ctcp, err = net.DialTCP("tcp4", nil, u.tcpaddr) if ctcp != nil { u.tcpconn = ctcp u.tcpconn.SetDeadline(time.Now().Add(u.Timeout)) } if err != nil { falcore.Error("Dial Failed: %v", err) } return ctcp, err } u.transport.MaxIdleConnsPerHost = 15 return u }
func NewUpstream(host string, port int, forceHttp bool) *Upstream { u := new(Upstream) u.Host = host u.Port = port u.ForceHttp = forceHttp ips, err := net.LookupIP(host) var ip net.IP = nil for i := range ips { ip = ips[i].To4() if ip != nil { break } } if err == nil && ip != nil { u.tcpaddr = &net.TCPAddr{} u.tcpaddr.Port = port u.tcpaddr.IP = ip } else { falcore.Warn("Can't get IP addr for %v: %v", host, err) } u.Timeout = 60 * time.Second u.host = fmt.Sprintf("%v:%v", u.Host, u.Port) u.transport = &http.Transport{} // This dial ignores the addr passed in and dials based on the upstream host and port u.transport.Dial = func(n, addr string) (c net.Conn, err error) { falcore.Fine("Dialing connection to %v", u.tcpaddr) var ctcp *net.TCPConn ctcp, err = net.DialTCP("tcp4", nil, u.tcpaddr) if err != nil { falcore.Error("Dial Failed: %v", err) return } c = &connWrapper{conn: ctcp, timeout: u.Timeout} return } u.transport.MaxIdleConnsPerHost = 15 return u }
func (u *Upstream) FilterRequest(request *falcore.Request) (res *http.Response) { var err error req := request.HttpRequest // Force the upstream to use http if u.ForceHttp || req.URL.Scheme == "" { req.URL.Scheme = "http" req.URL.Host = req.Host } before := time.Now() req.Header.Set("Connection", "Keep-Alive") if u.tcpconn != nil { u.tcpconn.SetDeadline(time.Now().Add(u.Timeout)) } var upstrRes *http.Response upstrRes, err = u.transport.RoundTrip(req) diff := falcore.TimeDiff(before, time.Now()) if err == nil { // Copy response over to new record. Remove connection noise. Add some sanity. res = falcore.SimpleResponse(req, upstrRes.StatusCode, nil, "") if upstrRes.ContentLength > 0 && upstrRes.Body != nil { res.ContentLength = upstrRes.ContentLength res.Body = upstrRes.Body } else if upstrRes.ContentLength == 0 && upstrRes.Body != nil { // Any bytes? var testBuf [1]byte n, _ := io.ReadFull(upstrRes.Body, testBuf[:]) if n == 1 { // Yes there are. Chunked it is. res.TransferEncoding = []string{"chunked"} res.ContentLength = -1 rc := &passThruReadCloser{ io.MultiReader(bytes.NewBuffer(testBuf[:]), upstrRes.Body), upstrRes.Body, } res.Body = rc } } else if upstrRes.Body != nil { res.Body = upstrRes.Body res.ContentLength = -1 res.TransferEncoding = []string{"chunked"} } // Copy over headers with a few exceptions res.Header = make(http.Header) for hn, hv := range upstrRes.Header { switch hn { case "Content-Length": case "Connection": case "Transfer-Encoding": default: res.Header[hn] = hv } } } else { if nerr, ok := err.(net.Error); ok && nerr.Timeout() { falcore.Error("%s Upstream Timeout error: %v", request.ID, err) res = falcore.SimpleResponse(req, 504, nil, "Gateway Timeout\n") request.CurrentStage.Status = 2 // Fail } else { falcore.Error("%s Upstream error: %v", request.ID, err) res = falcore.SimpleResponse(req, 502, nil, "Bad Gateway\n") request.CurrentStage.Status = 2 // Fail } } falcore.Debug("%s [%s] [%s] %s s=%d Time=%.4f", request.ID, req.Method, u.host, req.URL, res.StatusCode, diff) return }
func (f *Filter) FilterRequest(req *falcore.Request) (res *http.Response) { // Clean asset path asset_path := filepath.Clean(filepath.FromSlash(req.HttpRequest.URL.Path)) if filepath.Ext(asset_path) != ".css" { return } // Resolve PathPrefix if strings.HasPrefix(asset_path, f.PathPrefix) { asset_path = asset_path[len(f.PathPrefix):] } else { falcore.Debug("%v doesn't match prefix %v", asset_path, f.PathPrefix) res = falcore.SimpleResponse(req.HttpRequest, 404, nil, "Not found.") return } // Resolve FSBase if f.BasePath != "" { asset_path = filepath.Join(f.BasePath, asset_path) } else { falcore.Error("file_filter requires a BasePath") return falcore.SimpleResponse(req.HttpRequest, 500, nil, "Server Error\n") } scss_asset_path := strings.Replace(asset_path, ".css", ".scss", -1) sass_asset_path := strings.Replace(asset_path, ".css", ".sass", -1) if _, err := os.Stat(scss_asset_path); err == nil { asset_path = scss_asset_path } else if _, err := os.Stat(sass_asset_path); err == nil { asset_path = sass_asset_path } else { return } if file, err := os.Open(asset_path); err == nil { // Make sure it's an actual file if stat, err := file.Stat(); err == nil && stat.Mode()&os.ModeType == 0 { cmd := exec.Command("sass", "--scss", asset_path) var out bytes.Buffer cmd.Stdout = &out err = cmd.Run() if err != nil { falcore.Error("Can't compile SCSS file : %v", asset_path) return } css := out.String() res = &http.Response{ Request: req.HttpRequest, StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Body: ioutil.NopCloser(bytes.NewBufferString(css)), Header: make(http.Header), ContentLength: int64(len(css)), } res.Header.Set("Content-Type", mime.TypeByExtension(".css")) // css, err := sc.CompileFile(asset_path) // if err != nil { // falcore.Error("%v", err) // return // } // res = &http.Response{ // Request: req.HttpRequest, // StatusCode: 200, // Proto: "HTTP/1.1", // ProtoMajor: 1, // ProtoMinor: 1, // Body: ioutil.NopCloser(bytes.NewBufferString(css)), // Header: make(http.Header), // ContentLength: int64(len(css)), // } // res.Header.Set("Content-Type", mime.TypeByExtension(".css")) } else { file.Close() } } else { falcore.Finest("Can't open %v: %v", asset_path, err) } return }
func (c *Filter) FilterResponse(request *falcore.Request, res *http.Response) { req := request.HttpRequest if accept := req.Header.Get("Accept-Encoding"); accept != "" { // Is content an acceptable type for encoding? var compress = false var content_type = res.Header.Get("Content-Type") for _, t := range c.types { if content_type == t { compress = true break } } // Is the content already compressed if res.Header.Get("Content-Encoding") != "" { compress = false } if !compress { request.CurrentStage.Status = 1 // Skip return } // Figure out which encoding to use options := strings.Split(accept, ",") var mode string for _, opt := range options { if m := strings.TrimSpace(opt); m == "gzip" || m == "deflate" { mode = m break } } var compressor io.WriteCloser var buf = bytes.NewBuffer(make([]byte, 0, 1024)) switch mode { case "gzip": compressor = gzip.NewWriter(buf) case "deflate": comp, err := flate.NewWriter(buf, -1) if err != nil { falcore.Error("Compression Error: %v", err) request.CurrentStage.Status = 1 // Skip return } compressor = comp default: request.CurrentStage.Status = 1 // Skip return } // Perform compression r := make([]byte, 1024) var err error var i int for err == nil { i, err = res.Body.Read(r) compressor.Write(r[0:i]) } compressor.Close() res.Body.Close() res.ContentLength = int64(buf.Len()) res.Body = (*filteredBody)(buf) res.Header.Set("Content-Encoding", mode) } else { request.CurrentStage.Status = 1 // Skip } }