Webserver part-2
Refactoring the previous code
We will be putting the the content of HandleFunc into an independent object. Keeping all the handlers together inside a folder called handler. We create a new file called hello.go. Next we need to create a struct for the handle func. The http.HandleFunc under the hood, convertst he function that we are pasing as parameter, into http.Handler and is registering with the server,
In the documentation, the http.handler is an interface. It has single method. https://pkg.go.dev/net/http#Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
So we now need to create a handler , we need to create struct which implements the interface Handler. The hello.go looks like this
package handlers
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
type Hello struct {
}
//Adding method which satisfies the interface http.Handler, it has no return parameters
func (h*Hello) ServeHTTP(rw http.ResponseWriter, r *hhtp.Request) {
//adding the contents of HandleFunc here.
log.Println("hello world")
// Adding response writer and request variables to the http handler.
d, err := ioutil.ReadAll(r.Body)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
rw.Write([]byte("Bazinga!"))
//same things can be written replacing the above two lines and using the
//http error package.
//http.Error(rw, "Bazinga!", http.StatusBadRequest)
return
}
fmt.Fprintf(rw, "Hello %s", d)
}
Refactoring hello.go to make it modular. Adding dependency injection Defining a new function called NewHello which returns a Hello handler as a reference
package handlers
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
type Hello struct {
l *log.Logger
}
func NewHello(l *log.Logger) *Hello {
return &Hello{l}
}
//Adding method which satisfies the interface http.Handler, it has no return parameters
func (h *Hello) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
log.Println("hello world")
// Adding response writer and request variables to the http handler.
d, err := ioutil.ReadAll(r.Body)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
rw.Write([]byte("Bazinga!"))
//same things can be written replacing the above two lines and using the
//http error package.
//http.Error(rw, "Bazinga!", http.StatusBadRequest)
return
}
fmt.Fprintf(rw, "Hello %s", d)
}
We next add initiaite the logger variable l and the Hello handler. We create another handler called Goodbye similarly.
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/dileepkushwaha/server/handlers"
)
func main() {
l := log.New(os.Stdout, "product-api", log.LstdFlags)
hh := handlers.NewHello(l)
gh := handlers.NewGoodbye(l)
sm := http.NewServeMux()
sm.Handle("/", hh)
sm.Handle("/goodbye", gh)
http.ListenAndServe(":8080", sm)
}
Next we test the various performance attrtribute of the server such as Idle, Read and write timeouts.
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/dileepkushwaha/server/handlers"
)
func main() {
l := log.New(os.Stdout, "product-api", log.LstdFlags)
hh := handlers.NewHello(l)
gh := handlers.NewGoodbye(l)
sm := http.NewServeMux()
sm.Handle("/", hh)
sm.Handle("/goodbye", gh)
s := &http.Server{
Addr: ":8080",
Handler: sm,
IdleTimeout: 120 * time.Second,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
}
s.ListenAndServe()
}
We next test graceful shutdowns
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/dileepkushwaha/server/handlers"
)
func main() {
l := log.New(os.Stdout, "product-api", log.LstdFlags)
hh := handlers.NewHello(l)
gh := handlers.NewGoodbye(l)
sm := http.NewServeMux()
sm.Handle("/", hh)
sm.Handle("/goodbye", gh)
s := &http.Server{
Addr: ":8080",
Handler: sm,
IdleTimeout: 120 * time.Second,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
}
go func() {
err := s.ListenAndServe()
if err != nil {
l.Fatal(err)
}
}()
//ListenAndServe() will start and not block as it is under go routine.
//It also means that it will immidiately shutdown
//We can use os.Signal package for registration of certain signals.
//os.Singal.Notify takes a channel and a signal
sigChan := make(chan os.Signal)
signal.Notify(sigChan, os.Interrupt)
signal.Notify(sigChan, os.Kill)
sig := <-sigChan
l.Println("Recieved Terminate Gracefull Shutdown", sig)
//Shutdown(ctx context.Context) error
tc, _ := context.WithTimeout(context.Background(), 30*time.Second)
s.Shutdown(tc)
}
We now get a message While stopping he server with Ctrl+C.
go run main.go
product-api2024/08/15 01:27:59 hello world
^Cproduct-api2024/08/15 01:28:26 Recieved Terminate Gracefull Shutdown interrupt
product-api2024/08/15 01:28:26 http: Server closed