Avatar A personal blog about technical things I find useful. Also, random ramblings and rants...

Webserver part-2

Working with golang webserver.

image

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

all tags