Go's implementation of web server already uses go routines on every request. So isn't it the best performance you can get ? Yes it is. But then what is the purpose of this article ?
This article provides a way for the web server to provide instant response but process the heavy work in the background using go routines. It is mostly useful, for resource heavy task to be done.
This repository contains a Golang implementation of a web server that utilizes concurrent workers to handle incoming requests. The server responds to a "/hello" endpoint with an "Accepted" status code and the message "Welcome."
You can use my Github project to check the code directly : github.com/go-workerpool-webserver
Features
Concurrent Workers: The server employs a pool of worker goroutines (goroutines are lightweight threads in Go) to handle incoming requests concurrently. The number of worker go routines is defined by the workers variable.
Request Handling: The "/hello" endpoint is defined in the handleHello function. When a request is received on this endpoint, the server responds with an "Accepted" status code and the message "Welcome." Additionally, a timestamp (in nanoseconds) is sent to a channel (ch1) to be processed by one of the worker goroutines.
Worker Functionality: The workerFxn function represents the behaviour of the worker goroutines. Each worker waits for data from the ch1 channel, logs the received timestamp, and then simulates processing by sleeping for five seconds.
If you are new to Go and doesn't know how to run this project, then You can use my Github project to check the code directly : github.com/go-workerpool-webserver
Server
This will be responsible for handling requests
package main
import (
"log"
"net/http"
"time"
)
var (
// ch1 is a channel to send request IDs to workers
// Type of channel is int
// This channel can only send and receive integers
// If you want to send and receive any data type, use interface{}
ch1=make(chan int)
// workers is the number of concurrent workers to spawn
workers=50
)
// workerFxn is a function that runs in a goroutine and processes
func workerFxn(){
// range over the channel to receive request IDs
for i:=range ch1{
log.Println(i)
// Simulate some work by sleeping for 5 seconds
time.Sleep(time.Second * 5)
}
}
func handleHello(w http.ResponseWriter, r * http.Request){
// Send a response back to the client
w.WriteHeader(http.StatusOK)
w.Write([]byte("Welcome"))
// Send the request ID to the channel
go func(){
// Sending the current time through the channel to process
// You can send any data type through the channel as long as the
// receiving end is expecting the same data type
// In this case, we are sending the current time in nanoseconds
ch1<-time.Now().Nanosecond()
}()
}
func main(){
// Spawn the workers
for i:=0;i<workers;i++{
go workerFxn()
}
// Register the handler function
http.HandleFunc("/hello",handleHello)
// Start the server
log.Println("Starting server in 8080..")
err:=http.ListenAndServe(":8080",nil)
if err!=nil{
log.Println("Error running on port 8080")
}
}
Client
Use the client to generate 10K Get requests to the endpoint to test the server
package main
import (
"fmt"
"net/http"
"sync"
)
const (
// url is the URL of the server to send requests to
url = "http://localhost:8080/hello"
// totalRequests is the number of requests to send
totalRequests = 10000
)
func main() {
// Create a WaitGroup to wait for all requests to finish
var wg sync.WaitGroup
// Send totalRequests number of requests
for i := 1; i <= totalRequests; i++ {
wg.Add(1)
go sendRequest(i, &wg)
}
// Wait for all requests to finish
wg.Wait()
fmt.Println("All requests completed.")
}
func sendRequest(requestID int, wg *sync.WaitGroup) {
// Defer the call to Done to mark this function as finished
defer wg.Done()
// Send a GET request to the server
resp, err := http.Get(url)
if err != nil {
fmt.Printf("Request %d failed: %v\n", requestID, err)
return
}
fmt.Printf("Request %d completed %s\n", requestID,resp.Status)
}