func init() { go func() { // falcore setup pipeline := falcore.NewPipeline() pipeline.Upstream.PushBack(falcore.NewRequestFilter(func(req *falcore.Request) *http.Response { for _, data := range eserverData { if data.path == req.HttpRequest.URL.Path { header := make(http.Header) header.Set("Etag", data.etag) if data.chunked { buf := new(bytes.Buffer) buf.Write(data.body) res := falcore.SimpleResponse(req.HttpRequest, data.status, header, -1, ioutil.NopCloser(buf)) res.TransferEncoding = []string{"chunked"} return res } else { return falcore.StringResponse(req.HttpRequest, data.status, header, string(data.body)) } } } return falcore.StringResponse(req.HttpRequest, 404, nil, "Not Found") })) pipeline.Downstream.PushBack(new(EtagFilter)) esrv = falcore.NewServer(0, pipeline) if err := esrv.ListenAndServe(); err != nil { panic("Could not start falcore") } }() }
// Handle upload func (f UploadFilter) FilterRequest(request *falcore.Request) *http.Response { multipartReader, error := request.HttpRequest.MultipartReader() if error == http.ErrNotMultipart { return falcore.StringResponse(request.HttpRequest, http.StatusBadRequest, nil, "Bad Request // Not Multipart") } else if error != nil { return falcore.StringResponse(request.HttpRequest, http.StatusInternalServerError, nil, "Upload Error: "+error.Error()+"\n") } length := request.HttpRequest.ContentLength fmt.Println("content length:", length) part, error := multipartReader.NextPart() if error != io.EOF { var read int64 var percent, previousPercent float64 var filename string // find non existing filename for i := 1; ; i++ { filename = fmt.Sprintf("uploadedFile%v.mov", i) _, err := os.Stat(filename) if os.IsNotExist(err) { break } } fmt.Println(filename) destination, error := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644) if error != nil { return falcore.StringResponse(request.HttpRequest, http.StatusInternalServerError, nil, "Internal Server Error // Could not create file") } buffer := make([]byte, 1024) // 100 kB for { byteCount, error := part.Read(buffer) read += int64(byteCount) percent = math.Floor(float64(read)/float64(length)*100) + 1 if error == io.EOF { break } else if read > maxUploadBytes { fmt.Println("file too big") return falcore.StringResponse(request.HttpRequest, http.StatusRequestEntityTooLarge, nil, "Request Entity Too Large") } if percent != previousPercent { previousPercent = percent fmt.Printf("progress %v%%, read %fmb, %v byte of %v\n", percent, (float64(read) / (1024 * 1024)), read, length) } destination.Write(buffer) } } return falcore.StringResponse(request.HttpRequest, http.StatusOK, nil, "upload finished\n") }
func (f helloFilter) FilterRequest(req *falcore.Request) *http.Response { if req.HttpRequest.URL.Path == "/fast" { return falcore.StringResponse(req.HttpRequest, 200, nil, string("Hello, world!")) } if req.HttpRequest.URL.Path == "/slow" { time.Sleep(5 * time.Second) return falcore.StringResponse(req.HttpRequest, 200, nil, string("Hello, world!")) } if req.HttpRequest.URL.Path == "/panic" { panic("There is no need for panic") return falcore.StringResponse(req.HttpRequest, 200, nil, string("Hello, world!")) } return falcore.StringResponse(req.HttpRequest, 404, nil, "Not Found") }
// return response message with encoded json data and http status code 200 (OK) func SuccessResponse(request *falcore.Request, jsonData interface{}) *http.Response { response, jsonError := falcore.JSONResponse(request.HttpRequest, http.StatusOK, nil, jsonData) if jsonError != nil { response = falcore.StringResponse(request.HttpRequest, http.StatusInternalServerError, nil, fmt.Sprintf("JSON error: %s", jsonError)) } return response }
// NewLandingFilter generates a Falcore RequestFilter that produces a simple // landing page. func NewLandingFilter() falcore.RequestFilter { log().Debug("registering a new landing filter") return falcore.NewRequestFilter( func(req *falcore.Request) *http.Response { log().Info("running landing filter") return falcore.StringResponse( req.HttpRequest, 200, nil, "<html><head><title>"+ "Pullcord Landing Page"+ "</title></head><body><h1>"+ "Pullcord Landing Page"+ "</h1><p>"+ "This is the landing page for Pullcord, "+ "a reverse proxy for cloud-based web apps "+ "that allows the servers the web apps run on "+ "to be turned off when not in use."+ "</p><p>"+ "If you are unsure of how to proceed, "+ "please contact the site administrator."+ "</p></body></html>", ) }, ) }
func (f *FileFilter) 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.StringResponse(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.StringResponse(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() } } else { falcore.Finest("Can't open %v: %v", asset_path, err) } return }
func notImplementedError(request *falcore.Request) *http.Response { return falcore.StringResponse( request.HttpRequest, 501, nil, "<html><body><h1>Not Implemented</h1>"+ "<p>The requested behavior has not yet been implemented."+ "Please contact your system administrator.</p></body></html>", ) }
func internalServerError(request *falcore.Request) *http.Response { return falcore.StringResponse( request.HttpRequest, 500, nil, "<html><body><h1>Internal Server Error</h1>"+ "<p>An internal server error occured."+ "Please contact your system administrator.</p></body></html>", ) }
func (f *FileFilter) 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 { // The requested path doesn't fall into the scope of paths that are supposed to be handled by this filter return } // Resolve FSBase if f.BasePath != "" { asset_path = filepath.Join(f.BasePath, asset_path) } else { falcore.Error("file_filter requires a BasePath") return falcore.StringResponse(req.HttpRequest, 500, nil, "Server Error\n") } // Open File if file, err := os.Open(asset_path); err == nil { // If it's a directory, try opening the directory index if stat, err := file.Stat(); f.DirectoryIndex != "" && err == nil && stat.Mode()&os.ModeDir > 0 { file.Close() asset_path = filepath.Join(asset_path, f.DirectoryIndex) if file, err = os.Open(asset_path); err != nil { return } } // 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() } } else { falcore.Finest("Can't open %v: %v", asset_path, err) } return }
func init() { go func() { // falcore setup pipeline := falcore.NewPipeline() pipeline.Upstream.PushBack(falcore.NewRequestFilter(func(req *falcore.Request) *http.Response { for _, data := range eserverData { if data.path == req.HttpRequest.URL.Path { header := make(http.Header) header.Set("Etag", data.etag) return falcore.StringResponse(req.HttpRequest, data.status, header, string(data.body)) } } return falcore.StringResponse(req.HttpRequest, 404, nil, "Not Found") })) pipeline.Downstream.PushBack(new(EtagFilter)) esrv = falcore.NewServer(0, pipeline) if err := esrv.ListenAndServe(); err != nil { panic("Could not start falcore") } }() }
func loginPage( request *falcore.Request, sesh Session, badCreds bool, ) *http.Response { return falcore.StringResponse( request.HttpRequest, 501, nil, "<html><body><h1>Not Implemented</h1>"+ "<p>The requested behavior has not yet been implemented."+ "Please contact your system administrator.</p></body></html>", ) }
func TestUpstreamThrottle(t *testing.T) { // Start a test server sleepPipe := falcore.NewPipeline() sleepPipe.Upstream.PushBack(falcore.NewRequestFilter(func(req *falcore.Request) *http.Response { time.Sleep(time.Second) return falcore.StringResponse(req.HttpRequest, 200, nil, "OK") })) sleepSrv := falcore.NewServer(0, sleepPipe) go func() { sleepSrv.ListenAndServe() }() <-sleepSrv.AcceptReady // Build Upstream up := NewUpstream(NewUpstreamTransport("localhost", sleepSrv.Port(), 0, nil)) // pipe := falcore.NewPipeline() // pipe.Upstream.PushBack(up) resCh := make(chan *http.Response, 10) var i int64 = 1 for ; i < 12; i++ { start := time.Now() up.SetMaxConcurrent(i) for j := 0; j < 10; j++ { go func() { req, _ := http.NewRequest("GET", "/", nil) _, res := falcore.TestWithRequest(req, up, nil) resCh <- res // fmt.Println("OK") }() } for j := 0; j < 10; j++ { res := <-resCh if res.StatusCode != 200 { t.Fatalf("Error: %v", res) } } duration := time.Since(start) seconds := float64(duration) / float64(time.Second) goal := math.Ceil(10.0 / float64(i)) // fmt.Println(i, "Time:", seconds, "Goal:", goal) if seconds < goal { t.Errorf("%v: Too short: %v < %v", i, seconds, goal) } } }
// return response message with encoded json error message and a custom error code // if logMessage is nil, do not write to log func ErrorResponse(request *falcore.Request, status int, error string, errorDescription string, logMessage error) *http.Response { if logMessage != nil { log.Println(fmt.Sprintf("%s: %s, %s", error, errorDescription, logMessage)) } type Json struct { Error string ErrorDescription string } json := Json{error, errorDescription} response, jsonError := falcore.JSONResponse(request.HttpRequest, status, nil, json) if jsonError != nil { response = falcore.StringResponse(request.HttpRequest, http.StatusInternalServerError, nil, fmt.Sprintf("JSON error: %s", jsonError)) log.Println(fmt.Sprintf("%s: %s, %s, json error:", error, errorDescription, logMessage, jsonError)) } return response }
// very simple request filter func Filter(request *falcore.Request) *http.Response { return falcore.StringResponse(request.HttpRequest, 200, nil, "OK\n") }
func (f helloFilter) FilterRequest(req *falcore.Request) *http.Response { return falcore.StringResponse(req.HttpRequest, 200, nil, "hello world!\n") }
"</li>" } content += "</ul><h1>context</h1><ul>" sesh := req.Context["session"].(*MinSession) for key, val := range sesh.GetValues() { content += "<li class=\"sesh\">" + key + ": " + gostring(val) + "</li>" } content += "</ul></body></html>" return falcore.StringResponse( req.HttpRequest, 200, nil, content, ) }, ) var errorPage = falcore.NewRequestFilter( func(req *falcore.Request) *http.Response { // BUG(proidiot) issue-#45: Does not print context or cookies return falcore.StringResponse( req.HttpRequest, 500, nil, "<html><body><p>"+ "internal server error"+ "</p></body></html>",
func (handler *LoginHandler) FilterRequest( request *falcore.Request, ) *http.Response { errString := "" rawsesh, present := request.Context["session"] if !present { log().Crit( "login handler was unable to retrieve session from" + " context", ) return internalServerError(request) } sesh := rawsesh.(Session) authSeshKey := "authenticated-" + handler.Identifier xsrfKey := "xsrf-" + handler.Identifier usernameKey := "username-" + handler.Identifier passwordKey := "password-" + handler.Identifier authd, err := sesh.GetValue(authSeshKey) if err == nil && authd == true { log().Debug("login handler passing request along") return handler.Downstream.FilterRequest(request) } else if err != NoSuchSessionValueError { log().Err( fmt.Sprintf( "login handler error during auth status"+ " retrieval: %v", err, ), ) return internalServerError(request) } xsrfStored, err := sesh.GetValue(xsrfKey) if err != nil && err != NoSuchSessionValueError { log().Err( fmt.Sprintf( "login handler error during xsrf token"+ " retrieval: %v", err, ), ) return internalServerError(request) } else if err == NoSuchSessionValueError { log().Info("login handler received new request") } else if err = request.HttpRequest.ParseForm(); err != nil { log().Warning( fmt.Sprintf( "login handler error during ParseForm: %v", err, ), ) errString = "Bad request" } else if xsrfRcvd, present := request.HttpRequest.PostForm[xsrfKey]; !present { log().Info("login handler did not receive xsrf token") errString = "Invalid credentials" } else if len(xsrfRcvd) != 1 || 1 != subtle.ConstantTimeCompare( []byte(xsrfStored.(string)), []byte(xsrfRcvd[0]), ) { log().Info("login handler received bad xsrf token") errString = "Invalid credentials" } else if uVals, present := request.HttpRequest.PostForm[usernameKey]; !present { log().Info("login handler did not receive username") errString = "Invalid credentials" } else if pVals, present := request.HttpRequest.PostForm[passwordKey]; !present { log().Info("login handler did not receive password") errString = "Invalid credentials" } else if len(uVals) != 1 || len(pVals) != 1 { log().Info( "login handler received multi values for username or" + " password", ) errString = "Bad request" } else if err = handler.PasswordChecker.CheckPassword( uVals[0], pVals[0], ); err == NoSuchIdentifierError { log().Info("login handler received bad username") errString = "Invalid credentials" } else if err == BadPasswordError { log().Info("login handler received bad password") errString = "Invalid credentials" } else if err != nil { log().Err( fmt.Sprintf( "login handler error during CheckPassword: %v", err, ), ) return internalServerError(request) } else if err = sesh.SetValue(authSeshKey, true); err != nil { log().Err( fmt.Sprintf( "login handler error during auth set: %v", err, ), ) return internalServerError(request) } else { log().Notice( fmt.Sprintf( "login successful for: %s", uVals[0], ), ) return handler.Downstream.FilterRequest(request) } rawXsrfToken := make([]byte, XsrfTokenLength) if rsize, err := rand.Read( rawXsrfToken[:], ); err != nil || rsize != XsrfTokenLength { log().Err( fmt.Sprintf( "login handler error during xsrf generation:"+ " len expected: %u, actual: %u, err: %v", XsrfTokenLength, rsize, err, ), ) return internalServerError(request) } nextXsrfToken := hex.EncodeToString(rawXsrfToken) if err = sesh.SetValue(xsrfKey, nextXsrfToken); err != nil { log().Err( fmt.Sprintf( "login handler error during xsrf set: %v", err, ), ) return internalServerError(request) } errMarkup := "" if errString != "" { errMarkup = fmt.Sprintf( "<label class=\"error\">%s</label>", errString, ) } return falcore.StringResponse( request.HttpRequest, 200, nil, fmt.Sprintf( "<html><head><title>Pullcord Login</title></head>"+ "<body><form method=\"POST\" action=\"%s\">"+ "<fieldset><legend>Pullcord Login</legend>%s<label "+ "for=\"username\">Username:</label><input "+ "type=\"text\" name=\"%s\" id=\"username\" /><label "+ "for=\"password\">Password:</label><input "+ "type=\"password\" name=\"%s\" id=\"password\" />"+ "<input type=\"hidden\" name=\"%s\" value=\"%s\" />"+ "<input type=\"submit\" value=\"Login\"/></fieldset>"+ "</form></body></html>", request.HttpRequest.URL.Path, errMarkup, usernameKey, passwordKey, xsrfKey, nextXsrfToken, ), ) }
// catch all filter: if no filter matched, return 404 not found as default response func (f NotFoundFilter) FilterRequest(request *falcore.Request) *http.Response { return falcore.StringResponse(request.HttpRequest, http.StatusNotFound, nil, "Not Found") }
func TestPassthruLoginPage(t *testing.T) { /* setup */ testUser := "******" testPassword := "******" downstreamFilter := falcore.NewRequestFilter( func(request *falcore.Request) *http.Response { return falcore.StringResponse( request.HttpRequest, 200, nil, "<html><body><p>logged in</p></body></html>", ) }, ) sessionHandler := NewMinSessionHandler( "testSessionHandler", "/", "example.com", ) hash, err := GetPbkdf2Hash(testPassword, Pbkdf2MinIterations) assert.NoError(t, err) passwordChecker := InMemPwdStore{ map[string]*Pbkdf2Hash{ testUser: hash, }, } request1, err := http.NewRequest("GET", "/", nil) assert.NoError(t, err) /* run */ handler := &LoginHandler{ "testLoginHandler", &passwordChecker, downstreamFilter, } filter := &CookiemaskFilter{ sessionHandler, handler, falcore.NewRequestFilter( func(request *falcore.Request) *http.Response { return internalServerError(request) }, ), } _, response1 := falcore.TestWithRequest(request1, filter, nil) assert.Equal(t, 200, response1.StatusCode) assert.NotEmpty(t, response1.Header["Set-Cookie"]) content1, err := ioutil.ReadAll(response1.Body) assert.NoError(t, err) htmlRoot, err := html.Parse(bytes.NewReader(content1)) assert.NoError(t, err) xsrfToken, err := getXsrfToken(htmlRoot, "xsrf-"+handler.Identifier) assert.NoError(t, err) postdata2 := url.Values{} postdata2.Add("xsrf-"+handler.Identifier, xsrfToken) postdata2.Add("username-"+handler.Identifier, testUser) postdata2.Add("password-"+handler.Identifier, testPassword) request2, err := http.NewRequest( "POST", "/", strings.NewReader(postdata2.Encode()), ) request2.Header.Set( "Content-Type", "application/x-www-form-urlencoded", ) assert.NoError(t, err) for _, cke := range response1.Cookies() { request2.AddCookie(cke) } _, response2 := falcore.TestWithRequest(request2, filter, nil) assert.Equal(t, 200, response2.StatusCode) content2, err := ioutil.ReadAll(response2.Body) assert.NoError(t, err) assert.True( t, strings.Contains(string(content2), "logged in"), "content is: "+string(content2), ) request3, err := http.NewRequest("GET", "/", nil) assert.NoError(t, err) for _, cke := range response1.Cookies() { request3.AddCookie(cke) } _, response3 := falcore.TestWithRequest(request3, filter, nil) /* check */ assert.Equal(t, 200, response3.StatusCode) content3, err := ioutil.ReadAll(response3.Body) assert.NoError(t, err) assert.True( t, strings.Contains(string(content3), "logged in"), "content is: "+string(content3), ) }
func TestInitialLoginPage(t *testing.T) { /* setup */ testUser := "******" testPassword := "******" downstreamFilter := falcore.NewRequestFilter( func(request *falcore.Request) *http.Response { return falcore.StringResponse( request.HttpRequest, 200, nil, "<html><body><p>logged in</p></body></html>", ) }, ) sessionHandler := NewMinSessionHandler( "testSessionHandler", "/", "example.com", ) hash, err := GetPbkdf2Hash(testPassword, Pbkdf2MinIterations) assert.NoError(t, err) passwordChecker := InMemPwdStore{ map[string]*Pbkdf2Hash{ testUser: hash, }, } request, err := http.NewRequest("GET", "/", nil) assert.NoError(t, err) /* run */ handler := &LoginHandler{ "testLoginHandler", &passwordChecker, downstreamFilter, } filter := &CookiemaskFilter{ sessionHandler, handler, falcore.NewRequestFilter( func(request *falcore.Request) *http.Response { return internalServerError(request) }, ), } _, response := falcore.TestWithRequest(request, filter, nil) /* check */ assert.Equal(t, 200, response.StatusCode) content, err := ioutil.ReadAll(response.Body) assert.NoError(t, err) assert.True( t, strings.Contains(string(content), "xsrf-testLoginHandler"), "content is: "+string(content), ) assert.False( t, strings.Contains(string(content), "error"), "content is: "+string(content), ) assert.NotEmpty(t, response.Header["Set-Cookie"]) }
func (svc *MinMonitorredService) FilterRequest( req *falcore.Request, ) *http.Response { log().Debug("running minmonitor filter") up, err := svc.Status() if err != nil { log().Warning( fmt.Sprintf( "minmonitor filter received an error"+ " while requesting the status for \"%s:%d\":"+ " %v", svc.Address, svc.Port, err, ), ) return falcore.StringResponse( req.HttpRequest, 500, nil, "<html><head><title>Pullcord - Internal"+ " Server Error</title></head><body><h1>"+ "Pullcord - Internal Server Error</h1><p>An"+ " internal server error has occurred, but it"+ " might not be serious. However, If the"+ " problem persists, the site administrator"+ " should be contacted.</p></body></html>", ) } if svc.Always != nil { err = svc.Always.Trigger() if err != nil { log().Warning( fmt.Sprintf( "minmonitor filter received"+ " an error while running the"+ " always trigger on \"%s:%d\": %v", svc.Address, svc.Port, err, ), ) return falcore.StringResponse( req.HttpRequest, 500, nil, "<html><head><title>Pullcord -"+ " Internal Server Error</title>"+ "</head><body><h1>Pullcord -"+ " Internal Server Error</h1><p>"+ "An internal server error has"+ " occurred, but it might not be"+ " serious. However, if the problem"+ " persists, the site administrator"+ " should be contacted.</p></body>"+ "</html>", ) } } if up { if svc.OnUp != nil { err = svc.OnUp.Trigger() if err != nil { log().Warning( fmt.Sprintf( "minmonitor filter"+ " received an error"+ " while running the"+ " onDown trigger on"+ " \"%s:%d\": %v", svc.Address, svc.Port, err, ), ) return falcore.StringResponse( req.HttpRequest, 500, nil, "<html><head><title>Pullcord"+ " - Internal Server Error"+ "</title></head><body><h1>"+ "Pullcord - Internal Server"+ " Error</h1><p>An internal"+ " server error has occurred,"+ " but it might not be"+ " serious. However, if the"+ " problem persists, the site"+ " administrator should be"+ " contacted.</p></body></html>", ) } } log().Debug("minmonitor filter passthru") return svc.passthru.FilterRequest(req) } if svc.OnDown != nil { err = svc.OnDown.Trigger() if err != nil { log().Warning( fmt.Sprintf( "minmonitor filter received"+ " an error while running the"+ " onDown trigger on \"%s:%d\": %v", svc.Address, svc.Port, err, ), ) return falcore.StringResponse( req.HttpRequest, 500, nil, "<html><head><title>Pullcord -"+ " Internal Server Error</title>"+ "</head><body><h1>Pullcord -"+ " Internal Server Error</h1><p>"+ "An internal server error has"+ " occurred, but it might not be"+ " serious. However, If the problem"+ " persists, the site administrator"+ " should be contacted.</p></body>"+ "</html>", ) } } log().Info( fmt.Sprintf( "minmonitor filter has reached a down"+ " service (\"%s:%d\"), but any triggers have"+ " fired successfully", svc.Address, svc.Port, ), ) return falcore.StringResponse( req.HttpRequest, 503, nil, "<html><head><title>Pullcord - Service Not Ready"+ "</title></head><body><h1>Pullcord - Service Not"+ " Ready</h1><p>The requested service is not yet"+ " ready, but any trigger to start the service has"+ " been started successfully, so hopefully the"+ " service will be up in a few minutes.</p><p>If you"+ " would like further information, please contact the"+ " site administrator.</p></body></html>", ) }
func (u *Upstream) FilterRequest(request *falcore.Request) (res *http.Response) { var err error req := request.HttpRequest if u.Name != "" { request.CurrentStage.Name = fmt.Sprintf("%s[%s]", request.CurrentStage.Name, u.Name) } // Throttle // Wait for an opening, then increment in flight counter u.throttleC.L.Lock() u.throttleQueue += 1 for u.throttleMax > 0 && u.throttleInFlight >= u.throttleMax { u.throttleC.Wait() } u.throttleQueue -= 1 u.throttleInFlight += 1 u.throttleC.L.Unlock() // Decrement and signal when done defer func() { u.throttleC.L.Lock() u.throttleInFlight -= 1 u.throttleC.Signal() u.throttleC.L.Unlock() }() // 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") var upstrRes *http.Response upstrRes, err = u.Transport.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.StringResponse(req, upstrRes.StatusCode, nil, "") if upstrRes.ContentLength > 0 { res.ContentLength = upstrRes.ContentLength res.Body = upstrRes.Body } else if res.ContentLength == -1 { res.Body = upstrRes.Body res.ContentLength = -1 res.TransferEncoding = []string{"chunked"} } else { // 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 { // There was an error reading the body upstrRes.Body.Close() res.ContentLength = 0 res.Body = nil } } // 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 [%s] Upstream Timeout error: %v", request.ID, u.Name, err) res = falcore.StringResponse(req, 504, nil, "Gateway Timeout\n") request.CurrentStage.Status = 2 // Fail } else { falcore.Error("%s [%s] Upstream error: %v", request.ID, u.Name, err) res = falcore.StringResponse(req, 502, nil, "Bad Gateway\n") request.CurrentStage.Status = 2 // Fail } } falcore.Debug("%s %s [%s] [%s] %s s=%d Time=%.4f", request.ID, u.Name, req.Method, u.Transport.host, req.URL, res.StatusCode, diff) return }
) // Command line options var ( port = flag.Int("port", 8000, "the port to listen on") ) func main() { // parse command line options flag.Parse() // setup pipeline pipeline := falcore.NewPipeline() // upstream pipeline.Upstream.PushBack(helloFilter) // setup server server := falcore.NewServer(*port, pipeline) // start the server // this is normally blocking forever unless you send lifecycle commands if err := server.ListenAndServe(); err != nil { fmt.Println("Could not start server:", err) } } var helloFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response { return falcore.StringResponse(req.HttpRequest, 200, nil, "hello world!") })
func TestUpstreamThrottle(t *testing.T) { // Build a thing for var started = make(chan chan bool, REQ_COUNT+1) // Start a test server sleepPipe := falcore.NewPipeline() sleepPipe.Upstream.PushBack(falcore.NewRequestFilter(func(req *falcore.Request) *http.Response { // get chan // j, _ := strconv.Atoi(req.HttpRequest.URL.Query().Get("j")) // fmt.Println(req.HttpRequest.URL, j) c := make(chan bool) started <- c // wait on chan <-c // fmt.Println("DONE") return falcore.StringResponse(req.HttpRequest, 200, nil, "OK") })) sleepSrv := falcore.NewServer(0, sleepPipe) defer sleepSrv.StopAccepting() go func() { sleepSrv.ListenAndServe() }() <-sleepSrv.AcceptReady // Build Upstream up := NewUpstream(NewUpstreamTransport("localhost", sleepSrv.Port(), 0, nil)) // pipe := falcore.NewPipeline() // pipe.Upstream.PushBack(up) resCh := make(chan *http.Response, REQ_COUNT) var i int64 = 1 for ; i < 12; i++ { // fmt.Println("Testing with limit", i) up.SetMaxConcurrent(i) for j := 0; j < REQ_COUNT; j++ { var jj = j go func() { // fmt.Println("STARTING") req, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost/foo?j=%v", jj), nil) _, res := falcore.TestWithRequest(req, up, nil) res.Body.Close() resCh <- res // fmt.Println("OK") }() } for j := 0; j < REQ_COUNT; j++ { // make sure we haven't gone over the limit // fmt.Println(i, len(started)) if r := int64(len(started)); r > i { t.Errorf("%v: Over the limit: %v", i, r) } // send a finish signal (<-started) <- true // collect the result res := <-resCh if res.StatusCode != 200 { t.Fatalf("Error: %v", res) } } } }