How to implement server side timeouts? (Confused about http.Server timeouts) - go

I'm trying to implement server-side timeouts for my service. If the request takes longer than X seconds, the server should return 503 Service Unavailable.
I know that this can easily be accomplished by wrapping all of my endpoints in http.TimeoutHandler, but I'm confused why this isn't being done automatically by the Timeout fields of http.Server
Here is a trivial example that I am using for testing. If I cURL or POSTman this server, it hangs forever, rather than the 5 seconds I expect.
package main
import (
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/timeouttest", func(_ http.ResponseWriter, _ *http.Request) {
// busy infinite loop
// for { _ = struct {}{}}
// non-busy infinite loop
select {}
})
srv := &http.Server{
Addr: "localhost:5000",
Handler: mux,
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 90 * time.Second,
}
srv.ListenAndServe()
}
EDIT: forgot to link some Cloudflare articles that I have been using as inspiration.
complete guide to golang http timeouts
so you want to expose go on the internet

The answer is to use http.TimeoutHandler. I misunderstood the purpose of the http.Server Timeout fields.

Related

How do I implement a wss reverse proxy as a gin route?

I just recently started coding in go and thus I am not so skilled yet.
I have a use case where I want to implement a reverse proxy as a gin route.
So my route looks like this:
server.router.POST("/console", server.proxyConsoleUrl)
And my handler function something like this:
func (server *Server) proxyConsoleUrl(ctx *gin.Context) {
director := func(req *http.Request) {
r := ctx.Request
// This is not working, scheme wss is not supported
req.URL.Scheme = "wss"
req.URL.Host = "192.168.******:8006"
// The path which gets proxied should be overriden
req.URL.RawPath = "/api2/json/nodes/something/qemu/123/vncwebsocket?port=5900&vncticket=something"
req.Header["my-header"] = []string{r.Header.Get("my-header")}
// Golang camelcases headers
delete(req.Header, "My-Header")
// This header has to be added to every request which gets proxied
req.Header["Authorization"] = []string{"MYCUSTOMHEADER"}
}
proxy := &httputil.ReverseProxy{Director: director, Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
proxy.ServeHTTP(ctx.Writer, ctx.Request)
}
So my first problem is, that httputil.ReverseProxy doesn't support web socket as far as I know and noticed when running my code:
httputil: unsupported protocol scheme "wss"
The Second problem is, that I want to override the backend url as well as add custom headers which are added by the proxy.
Maybe someone has an idea hot to implement this, if it's even possible. -Thanks in advance
WebSocket support was added to httputil.ReverseProxy in Go version 1.12.
Use the result of url.Parse("https://192.168.******:8006/api2/json/nodes/something/qemu/123/vncwebsocket?port=5900&vncticket=something") to set the target URL. This fixes the following issues:
The WebSocket protocol uses "http" or "https" on the wire, not "wss".
RawPath is ignored when RawPath is a not a valid escaping of Path. See EscapedPath for the details. Because the RawPath in the question includes a query string, it will never be a valid escaping of Path. The client's path is alway used as is. Ditto for the client's query string.
Create the proxy once and reuse it. The important point is to create an reuse a single transport per the Transport documentation. Reusing the proxy accomplishes that goal.
func createProxy() *httputil.ReverseProxy {
target, _ := url.Parse("https://192.168.******:8006/api2/json/nodes/something/qemu/123/vncwebsocket?port=5900&vncticket=something")
director := func(req *http.Request) {
req.URL = target
req.Header["my-header"] = []string{req.Header.Get("my-header")}
delete(req.Header, "My-Header")
req.Header["Authorization"] = []string{"MYCUSTOMHEADER"}
}
return &httputil.ReverseProxy{Director: director, Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
}
var proxy = createProxy()
func (server *Server) proxyConsoleUrl(ctx *gin.Context) {
proxy.ServeHTTP(ctx.Writer, ctx.Request)
}

How to use go-lang server as both file server and back-end logic server

In php we can host application and use the same server,port to handle the back-end logic calls.
I've used the following way to achieve this in go-lang. Is there a better way to achieve this?.
r := mux.NewRouter()
http.HandleFunc("/dependencies/", DependencyHandler) //file serving
http.HandleFunc("/portals/", PortalsHandler) //file serving
r.HandleFunc("/registeruser", UserRegistrationHandler)
r.HandleFunc("/deleteuser/{username}", DeleteUserHandler)
http.Handle("/",r)
s := &http.Server{
Addr: ":" + strconv.Itoa(serverConfigs.HttpPort),
Handler: nil,
ReadTimeout: time.Duration(serverConfigs.ReadTimeOut) * time.Second,
WriteTimeout: time.Duration(serverConfigs.WriteTimeOut) * time.Second,
MaxHeaderBytes: 1 << 20,
}
You can serve static files and implement your backend logic handlers with mux router. Use PathPrefix() and StripPrefix() for that:
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
r := mux.NewRouter()
r.PathPrefix("/portals/").Handler(http.StripPrefix("/portals/", http.FileServer(http.Dir("./portals/"))))
r.PathPrefix("/dependencies/").Handler(http.StripPrefix("/dependencies/", http.FileServer(http.Dir("./dependencies/"))))
r.HandleFunc("/registeruser", UserRegistrationHandler)
r.HandleFunc("/deleteuser/{username}", DeleteUserHandler)
http.Handle("/", r)
log.Println("Listening...")
http.ListenAndServe(":8000", r)
}

Http Server Read-Write timeouts and Server Side Events

I'm writing a test app with SSE, but my problem is that
ReadTimeout and WriteTimeout are closing the clients connection every 10 Seconds and because of this the main page are losing data.
How can I manage this Issue, serving SSE and webpages together without goroutines leak risk and SSE working done?
Server:
server := &http.Server{Addr: addr,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
Handler: s.mainHandler(),
}
Handler:
func sseHandler(w http.ResponseWriter, r *http.Requests) {
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported!", http.StatusInternalServerError)
log.Println("Streaming not supported")
return
}
messageChannel := make(chan string)
hub.addClient <- messageChannel
notify := w.(http.CloseNotifier).CloseNotify()
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
for i := 0; i < 1440; {
select {
case msg := <-messageChannel:
jsonData, _ := json.Marshal(msg)
str := string(jsonData)
fmt.Fprintf(w, "data: {\"str\": %s, \"time\": \"%v\"}\n\n", str, time.Now())
f.Flush()
case <-time.After(time.Second * 60):
fmt.Fprintf(w, "data: {\"str\": \"No Data\"}\n\n")
f.Flush()
i++
case <-notify:
f.Flush()
i = 1440
hub.removeClient <- messageChannel
}
}
}
Both ReadTimeout and WriteTimeout define the duration within which the whole request must be read from or written back to the client. These timeouts are applied to the underlying connection (http://golang.org/src/pkg/net/http/server.go?s=15704:15902) and this is before any headers are received, so you cannot set different limits for separate handlers – all connections within the server will share the same timeout limits.
Saying this, if you need customised timeouts per request, you will need to implement them in your handler. In your code you're already using timeouts for your job, so this would be a matter of creating a time.After at the beginning of the handler, keep checking (in the handler itself or even pass it around) and stop the request when necessary. This actually gives you more precise control over the timeout, as WriteTimeout would only trigger when trying to write the response (e.g. if the timeout is set to 10 seconds and it takes a minute to prepare the response before any write, you won't get any error until the job is done so you'll waste resources for 50 seconds). From this perspective, I think WriteTimeout itself is good only when your response is ready quite quickly, but you want to drop the connection when network become very slow (or the client deliberately stops receiving data).
There is a little helper function in the library, http.TimeoutHandler, which behaves similarly to WriteTimeout, but sends back 503 error if the response takes longer than predefined time. You could use it to set up behaviour similar to WriteTimeout per handler, for example:
package main
import (
"log"
"net/http"
"time"
)
type Handler struct {
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
time.Sleep(3*time.Second)
// This will return http.ErrHandlerTimeout
log.Println(w.Write([]byte("body")))
}
func main() {
h := &Handler{}
http.Handle("/t1", http.TimeoutHandler(h, 1*time.Second, ""))
http.Handle("/t2", http.TimeoutHandler(h, 2*time.Second, ""))
http.ListenAndServe(":8080", nil)
}
This looks very handy, but I found one disadvantage that would affect your code: http.ResponseWriter passed from http.TimeoutHandler doesn't implement http.CloseNotifier. If it's required, you could dive into the implementation and see how they solved the timeout problem in order to come up with a similar, but enhanced solution.

golang - net/http/pprof doesn't work

I have customer http service:
s := &http.Server{
Addr: config.Port,
Handler: Controller.Log(http.DefaultServeMux),
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
}
http.HandleFunc("/exapmle/router/", exampleFunc)
err := s.ListenAndServe()
if err != nil {
log.Critical(err)
os.Exit(1)
}
It's does't work:
go tool pprof http://localhost:8201/debug/pprof/profile
return error:
Failed to fetch http://localhost:8201/debug/pprof/profile?seconds=30
Thanks.
edit:
I think the problem is i rewrite default http server, net/http/pprof package inject http handler:
func init() {
http.Handle("/debug/pprof/", http.HandlerFunc(Index))
http.Handle("/debug/pprof/cmdline", http.HandlerFunc(Cmdline))
http.Handle("/debug/pprof/profile", http.HandlerFunc(Profile))
http.Handle("/debug/pprof/symbol", http.HandlerFunc(Symbol))
}
the handler does not work in my code.
You set "WriteTimeout" less then profile write time.
on pprof.StartCPUProfile() execute, It already start write, see:
http://golang.org/src/pkg/runtime/pprof/pprof.go#L565
http://golang.org/src/pkg/runtime/pprof/pprof.go#L594
So http.WriteTimeout must be greater than profile write time.
Sorry for my poor English.

How to use gorilla mux with http.TimeoutHandler

In an HTTP server written in go, I use gorilla/mux for routing,
I want to use http.TimeoutHandler (and/or other "middleware") but I can't understand where I can fit them.
To make it clear:
I create a new Router by gorillaMux := mux.NewRouter()
add my routes by calls like gorillaMux.HandleFunc("/", rootHandler)
I create the server by server := &http.Server{Addr:":1234"} and server.ListenAndServe()
Where can I insert the http.TimeoutHandler or any other middleware for that matter?
Here is how you can do this:
package main
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
"time"
)
func rootHandler(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintf(w, "Hello!")
}
func main() {
mux := mux.NewRouter()
mux.HandleFunc("/", rootHandler)
muxWithMiddlewares := http.TimeoutHandler(mux, time.Second*3, "Timeout!")
http.ListenAndServe(":8080", muxWithMiddlewares)
}
If you have more than one HTTP handler, you can stack them up:
// this is quite synthetic and ugly example, but it illustrates how Handlers works
muxWithMiddlewares := http.StripPrefix("/api", http.TimeoutHandler(mux, time.Second*3, "Timeout!"))
This solution does not answer the use TimeoutHandler. I posted this here in case anybody wants fine-grain control of their server timeout. This alternative will allow one to define the IdleTimeout and RequestHeaderTimeout if necessary. I am using both gorilla/mux and gorilla/handlers in this example. Hope it helps.
// ReadTimeout is a timing constraint on the client http request imposed by the server from the moment
// of initial connection up to the time the entire request body has been read.
// [Accept] --> [TLS Handshake] --> [Request Headers] --> [Request Body] --> [Response]
// WriteTimeout is a time limit imposed on client connecting to the server via http from the
// time the server has completed reading the request header up to the time it has finished writing the response.
// [Accept] --> [TLS Handshake] --> [Request Headers] --> [Request Body] --> [Response]
func main() {
mux := router.EpicMux()
srv := &http.Server{
Handler: handlers.LoggingHandler(os.Stdout, mux),
Addr: "localhost:8080",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
func EpicMux() http.Handler {
r := mux.NewRouter()
r.HandleFunc("/", BaseURLRouter).Methods(http.MethodGet)
// create the subroutes for v1 and v2
v1 := r.PathPrefix("api/v1").Subrouter()
// register handlers to appropriate version
v1.HandleFunc("/person", PersonHandlerV1).Methods(http.MethodPost)
v2 := r.PathPrefix("api/v2").Subrouter()
v2.HandleFunc("/person", PersonHandlerV2).Methods(http.MethodPost)
return r
}

Resources