Writing basic HTTP Server on Golang using Servion Framework
The first basic http server on servion framework in golang
Today, we will try to build the first server application on servion framework. This framework was build to simplify bootstrapping of the web application, having CLI (Command Line Interface) in place and provide DI (Dependency Injection) capabilities from day one.
Having those things combined in simple from in golang has a certain value.
You do not need to start from the scratch and re-invent the wheel, instead, you can use robust and flexible framework to achieve your goals.
Let’s create the first application.
We can start from dependencies. In this page select the latest version of the servion and modify the go.mod file.
module github.com/yourhandle/yourprojectgo 1.23.0toolchain go1.24.1require ( github.com/gorilla/websocket v1.5.3 go.arpabet.com/cligo v0.1.6 go.arpabet.com/glue v1.2.4 go.arpabet.com/servion v0.1.2)Then run the command to update the dependencies
go mod tidyNow, we are ready to create main.go file
package mainimport ( "go.arpabet.com/cligo" "go.arpabet.com/glue" "go.arpabet.com/servion")func main() { properties := glue.MapPropertySource{ "http-server.bind-address": "0.0.0.0:8000", } beans := []interface{}{ properties, servion.RunCommand(servion.HttpServerScanner("http-server")), servion.ZapLogFactory(), } cligo.Main(cligo.Beans(beans...))}In this simple example we are creating application context containing three beans:
- properties with host and port
- RunCommand instance to provide simple CLI with “run” command.
- Zap Logger
The Zap Logger is using by default in servion since it is provides JSON logging output, that is good fit for any cloud hosting.
The HTTP server is located in RunCommand child context, so it would not be created for other commands if we add to the server. This is considered as a good practice.
Creation of the HTTP server is based on bean name, that is in our case “http-server”. The same bean name we are using in properties.
Each HTTP server requires as minimum “bind-address” property to correctly start listening the port.
Let’s build the project
go buildAnd run it
./basic run2025-03-26T14:39:01.422-0700 INFO [email protected]/http_server_factory.go:93 HTTPServerFactory {"listenAddr": "0.0.0.0:8000", "bean": "http-server", "handlers": [], "assets": [], "options": {}, "tls": false}2025-03-26T14:39:01.422-0700 INFO [email protected]/utils.go:166 ServionStarted {"Servers": 1}2025-03-26T14:39:01.423-0700 INFO [email protected]/http_server.go:105 HttpServerServe {"addr": "0.0.0.0:8000", "network": "tcp", "tls": false}As we can see our server is started, to stop is press Ctrl-C in terminal
^C2025-03-26T14:39:03.468-0700 INFO [email protected]/utils.go:193 StopSignal {"signal": "interrupt"}2025-03-26T14:39:03.468-0700 INFO [email protected]/http_server.go:66 HttpServerShutdown {"addr": "0.0.0.0:8000", "network": "tcp"}2025-03-26T14:39:03.468-0700 INFO [email protected]/run_command.go:69 RunServers {"restarting": false}What we learn from the logs, there is a restarting mode of the server, that would close child context and re-create it again to restart all servers without stopping the application. This functionality designed in case if you do not want to kill the pod in Kubernetes cluster, but at the same time need to reload some servers, for example update the secrets or credentials, tls certificates and so on.
To restart the server user this command.
kill -1 <pid>Now we are ready to modify this server a little bit.
Let’s add additional HTTP Server to the servion.
func main() { properties := &glue.PropertySource{Map: map[string]interface{}{ "web-server.bind-address": "0.0.0.0:8000", "cdr-server.bind-address": "0.0.0.0:8001", }} beans := []interface{}{ properties, servion.RunCommand(servion.HttpServerScanner("web-server"), servion.HttpServerScanner("cdr-server")), servion.ZapLogFactory(), } cligo.Main(cligo.Beans(beans...))}Now we have two servers: web-server and cdr-server. They both are sharing the same child context, root properties and root logger.
Let’s build the project
go buildAnd run
./two_servers run2025-03-26T17:04:10.144-0700 INFO [email protected]/http_server_factory.go:93 HTTPServerFactory {"listenAddr": "0.0.0.0:8000", "bean": "web-server", "handlers": [], "assets": [], "options": {}, "tls": false}2025-03-26T17:04:10.144-0700 INFO [email protected]/http_server_factory.go:93 HTTPServerFactory {"listenAddr": "0.0.0.0:8001", "bean": "cdr-server", "handlers": [], "assets": [], "options": {}, "tls": false}2025-03-26T17:04:10.144-0700 INFO [email protected]/utils.go:166 ServionStarted {"Servers": 2}2025-03-26T17:04:10.144-0700 INFO [email protected]/http_server.go:105 HttpServerServe {"addr": "0.0.0.0:8001", "network": "tcp", "tls": false}2025-03-26T17:04:10.145-0700 INFO [email protected]/http_server.go:105 HttpServerServe {"addr": "0.0.0.0:8000", "network": "tcp", "tls": false}Now we see that both servers are started and also demonstrated that servion can have multiple servers in one context.
Web-sockets
Additionally, the servion framework supports web-sockets indirectly. It provides the flexible interface of initialization of http server, that look up only the HttpHandler. Inside HttpHandler the websocket could be initialized.
Let’s create the structure for the handler
type implWebsocketHandler struct { upgrader *websocket.Upgrader}Inside this structure we have additional private field “upgrader”.
This field is using to upgrade the connection to the web-socket inside the ServerHTTP method.
Let’s create the constructor
func WebsocketHander() servion.HttpHandler { return &implWebsocketHandler{ upgrader: &websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true // allow all origins for now }, }, }}In the constructor we need to initialize the “upgrader” and provide the “CheckOrigin” function. This is needed because web-sockets by default could be used from any page. Literally, web-sockets are ideal technology to distribute some functionality by SaaS.
And finally, we need to implement the method for websocket handler
func (t *implWebsocketHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { conn, err := t.upgrader.Upgrade(writer, request, nil) if err != nil { log.Printf("[WebSocket] Error upgrading connection: %v", err) return } defer conn.Close() log.Println("Client connected") for { // Read message from client _, msg, err := conn.ReadMessage() if err != nil { log.Println("Read error:", err) break } log.Println("Received:", string(msg)) // Echo message back to client err = conn.WriteMessage(websocket.TextMessage, msg) if err != nil { fmt.Println("Write error:", err) break } } log.Println("Client disconnected")}Please, so not forget to close the connection by “defer conn.Close()”.
Additionally, we need to provide the Pattern for the handler:
func (t *implWebsocketHandler) Pattern() string { return "/v1/examples/websocket"}Since we are building our application by components, we can not have dedicated code for the mux, we would define pattern in each component separately. In this case this code became reusable, we can package it separately and ship to another applications.
And in order this WebsocketHandler to be used we need to modify our main.go
func main() { properties := glue.MapPropertySource{ "http-server.bind-address": "0.0.0.0:8000", "http-server.options": "handlers", } beans := []interface{}{ properties, servion.RunCommand(servion.HttpServerScanner("http-server"), WebsocketHander()), servion.ZapLogFactory(), } cligo.Main(cligo.Beans(beans...))}Additional setting “<bean_name>.options” added to the property. This option is responsible for the enabling certain options in the HttpServerFactory.
Options:
- “handlers” — it would lookup in context classes HttpHandler and combine them in “mux”
- “assets” — it would lookup in context resources having name with suffix “asset” and use their http.FileSystem to serve static content
- “tls” — it would lookup in context *tls.Config instance and use it for TLS connection
This framework was created to provide the simple way of building micro-services in Kubernetes and Docker, having minimum functionality only needed for this purpose. Support popular protocols and being lightweight.
Servion examples: https://github.com/arpabet/servion-examples
Servion Vue Vite example: https://github.com/arpabet/servion-vue-example
