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
}
Related
I'm studying Golang and have question on context.
I want to send request from server1 to server2 with context and want to read context value set by server1.
But it seems context of sercer2 doesn't carry server1's context value.
When I send request to server1 by curl http://localhost:8080
server1's console.
Send request to http://localhost:8082
server2's console.
request coming in
<nil>
How can I retrieve context value set by server1?
And if it is possible, I also want to know whether it is a correct way of exchanging value's like authentication between servers.
Middleware pattern is more desirable?
Thank you.
Codes
package main
import (
"context"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.HandleFunc("/", hello)
fmt.Println("Starting listening on port 8080...")
http.ListenAndServe(":8080", r)
}
// Context's key.
type Sample string
var sampleKey Sample = "sample"
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Println("Send request to http://localhost:8082")
ctx := context.WithValue(context.Background(), sampleKey, "1234")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8082", nil)
if err != nil {
fmt.Println("Error while sending request: ", err)
}
// Send request.
var c http.Client
c.Do(req)
}
package main
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.HandleFunc("/", receive)
fmt.Println("Start listening on port 8082...")
http.ListenAndServe(":8082", r)
}
type Sample string
var sampleKey Sample
func receive(w http.ResponseWriter, r *http.Request) {
fmt.Println("request coming in")
fmt.Println(r.Context().Value(sampleKey))
}
The context is supposed to be used in a function call stack to share information and cancellation point between these functions. There is no automatic mechanism to serialize the value bound in the context over http. If you want to send data over http between these two servers, you mostly have three solution, you can:
encode that as an url parameter in the path somewhere
GET /hello/1234
send that in the http body, for example in JSON
POST /hello
{
"sample": 1234
}
as url key/value encoded as url parameters
/hello?sample=1234
I have a specific requirement with Gorilla mux routing where i want to add different Middlewares for different routes that are under one subrouter( GET subrouter in my case). Below is my code for routing:
// create a serve mux
sm := mux.NewRouter()
// register handlers
postR := sm.Methods(http.MethodPost).Subrouter()
postR.HandleFunc("/signup", uh.Signup)
postR.HandleFunc("/login", uh.Login)
postR.Use(uh.MiddlewareValidateUser)
getR := sm.Methods(http.MethodGet).Subrouter()
getR.HandleFunc("/refresh-token", uh.RefreshToken)
getR.HandleFunc("/user-profile", uh.GetUserProfile)
In the above router logic both my /refresh-token and /user-profile token come under getR router. Also i have two middleware functions called ValidateAccessToken and ValidateRefreshToken. I want to use the ValidateRefreshToken middleware function for "/refresh-token" route and ValidateAccessToken for all other routes under GET subrouter. I want to do it with Gorilla mux routing itself. Please suggest me the appropriate approach to accomplish the above scenario. Thanks for your time and effort.
The solution provided by notorious.no also works perfectly for the given requirement. Also i came across another solution which uses PathPrefix and solves the same issue and would like to get suggestions for the same.
// create a serve mux
sm := mux.NewRouter()
// register handlers
postR := sm.Methods(http.MethodPost).Subrouter()
postR.HandleFunc("/signup", uh.Signup)
postR.HandleFunc("/login", uh.Login)
postR.Use(uh.MiddlewareValidateUser)
refToken := sm.PathPrefix("/refresh-token").Subrouter()
refToken.HandleFunc("", uh.RefreshToken)
refToken.Use(uh.MiddlewareValidateRefreshToken)
getR := sm.Methods(http.MethodGet).Subrouter()
getR.HandleFunc("/greet", uh.Greet)
getR.Use(uh.MiddlewareValidateAccessToken)
Came to this solution using the reference : https://github.com/gorilla/mux/issues/360
I had a similar use case and this is an example of how I solved it:
package main
import (
"log"
"net/http"
"time"
)
import (
"github.com/gorilla/mux"
)
// Adapter is an alias so I dont have to type so much.
type Adapter func(http.Handler) http.Handler
// Adapt takes Handler funcs and chains them to the main handler.
func Adapt(handler http.Handler, adapters ...Adapter) http.Handler {
// The loop is reversed so the adapters/middleware gets executed in the same
// order as provided in the array.
for i := len(adapters); i > 0; i-- {
handler = adapters[i-1](handler)
}
return handler
}
// RefreshToken is the main handler.
func RefreshToken(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("hello world"))
}
// ValidateRefreshToken is the middleware.
func ValidateRefreshToken(hKey string) Adapter {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
// Check if a header key exists and has a value
if value := req.Header.Get(hKey); value == "" {
res.WriteHeader(http.StatusForbidden)
res.Write([]byte("invalid request token"))
return
}
// Serve the next handler
next.ServeHTTP(res, req)
})
}
}
// MethodLogger logs the method of the request.
func MethodLogger() Adapter {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
log.Printf("method=%s uri=%s\n", req.Method, req.RequestURI)
next.ServeHTTP(res, req)
})
}
}
func main() {
sm := mux.NewRouter()
getR := sm.Methods(http.MethodGet).Subrouter()
getR.HandleFunc("/refresh-token", Adapt(
http.HandlerFunc(RefreshToken),
MethodLogger(),
ValidateRefreshToken("Vikee-Request-Token"),
).ServeHTTP)
srv := &http.Server{
Handler: sm,
Addr: "localhost:8888",
WriteTimeout: 30 * time.Second,
ReadTimeout: 30 * time.Second,
}
log.Fatalln(srv.ListenAndServe())
}
The Adapt function lets you chain multiple handlers together. I've demonstrated 2 middleware funcs (ValidateRefreshToken and MethodLogger). The middleware are basically closures. You can use this method with any framework that accepts http.Handler such as mux and chi.
Another usage example
mux := mux.NewRouter()
healthRoute := mux.Path("/health").Handler(healthHandler)
mux.PathPrefix("/").Handler(defaultHandler)
// Run logging middleware except on the health route because health is spammy
mux.Use(libHttp.MiddlewareExcept(libHttp.LoggingMiddlewareWith404(logger), healthRoute))
And implementation
// MiddlewareExcept returns a new middleware that calls the provided middleware except on the provided routes
func MiddlewareExcept(middleware mux.MiddlewareFunc, routes ...*mux.Route) mux.MiddlewareFunc {
routeMatch := mux.RouteMatch{}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
for _, route := range routes {
if route.Match(r, &routeMatch) {
if next != nil {
next.ServeHTTP(rw, r)
}
} else {
middleware(next).ServeHTTP(rw, r)
}
}
})
}
}
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.
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)
}
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.