zttp

package module
v0.0.0-...-2eb2490 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2025 License: MIT Imports: 20 Imported by: 0

README

ZTTP

Introduction

ZTTP is a lightweight, zero-dependency, and extremely fast backend framework written in Go, built directly over raw TCP sockets. Designed as a toy project for educational purposes, it draws inspiration from modern web frameworks like Gofiber and Express.js.

This project follows the Front Controller design pattern, a widely adopted architectural approach in web frameworks such as Spring Boot, Express.js, and more.

All incoming TCP connections are funneled concurrently through a centralized request handling function handleClient(), which performs the following responsibilities:

  • Parses the HTTP request (method, path, params, queries, headers, body, cookies, etc.).
  • Extracts query parameters and dynamic route parameters.
  • Matches the request to a registered route handler using method and path.
  • Delegates the request to the matched handler with a unified request/response context.
  • Centrally Manages errors, timeouts, middlewares, connection lifecycle, and more.

By applying this pattern, the application enforces a clean separation of concerns, consistent request preprocessing, and centralized control over the request lifecycle. This design simplifies extensibility (e.g., adding middleware, authentication, logging) and improves maintainability as the application scales.

To state some numbers, I tested the same routes and benchmarks with different frameworks using wrk and took the average:

  • 300k RPS, 3.5 ms latency using GoFiber
  • 135k RPS, 8.7 ms latency using ZTTP
  • 67k RPS, 34 ms latency using Spring WebMVC
  • 55k RPS, 19 ms latency using Spring WebFlux
  • 10k RPS, 135 ms latency using Express.js (Node)
  • 1.7k RPS, 128 ms latency using Flask

zttp_graph


Benchmarks included different core numbers, time periods, routes, etc, all on the same machine separately, and those are the average values.

Why ZTTP?

ZTTP was created as a deep-dive into how web frameworks work under the hood. From TCP socket handling and manual HTTP parsing to request routing and middleware architecture. It is a hands-on exercise in systems-level web development using Go, with minimal abstractions.

I decided not to use any external HTTP engines, not even Go's net/http standard library, and handle all the logic from scratch starting from the TCP layer to gain maximum knowledge and experience.

Everything in this project is perfectly aligned with the RFC standards and HTTP/1.1 structure, as I spent days reading the RFC standards specific to each feature before starting to implement it.

Whether you're learning how the web works or exploring Go's networking capabilities, ZTTP is designed to be small enough to understand yet expressive enough to grow.

Installation

To use ZTTP in your Go project, simply run:

go get github.com/muhammadzkralla/zttp

Then, import it in your code:

import "github.com/muhammadzkralla/zttp"

[!NOTE] ZTTP is still under active development and may lack some features. It fetches the latest commit from the master branch. Semantic versioning and releases will be introduced later when I feel it's ready.

Usage

Here’s a minimal example of how to spin up a simple ZTTP server:

package main

import (
	"github.com/muhammadzkralla/zttp"
)

func main() {
	app := zttp.NewApp()

	app.Get("/", func(req *zttp.Req, res *zttp.Res) {
		res.Status(200).Send("Hello from ZTTP!")
	})

	app.Start(8080)
}

You can now test your server like this:

curl "localhost:8080"

You will get this printed in your terminal:

Hello from ZTTP!

Features

Core Functionality

  • Raw TCP HTTP/1.1 server with concurrent connection handling
  • Front Controller Design Pattern implementation
  • Zero dependency (pure GO standard library)

Routing

app.Get("/path", handler)
app.Post("/path", handler)
app.Put("/path", handler)
app.Patch("/path", handler)
app.Delete("/path", handler)

Path Parameters

// Route: "/post/:postId/comment/:commentId"
params := req.Params                // All params (map[string]string)
postId := req.Param("postId")       // postId param
commentId := req.Param("commentId")  // commentId param

Queries Parameters

// URL: /user?name=John&age=30
queries := req.Queries              // All queries (map[string]string)
name := req.Query("name")           // name query
age := req.Query("age")           // age query

Request Handling

  • Body parsing: req.Body (raw string)
body := req.Body    // raw string request body
  • JSON parsing:
// Parse request body into JSON and store the result in user variable
var user User
err := req.ParseJson(&user)
  • Form data & file uploads:
value := req.FormValue("field")     // Get `field` part from request
file, err := req.FormFile("file")   // Get `file` part from request
err = req.Save(file, "./uploads")   // Save file to disk in `./uploads` directory
  • Accept headers processing:
log.Println(req.Accepts("html"))                         // "html"
log.Println(req.AcceptsCharsets("utf-16", "iso-8859-1")) // "iso-8859-1"
log.Println(req.AcceptsEncodings("compress", "br"))      // "compress"
log.Println(req.AcceptsLanguages("en", "nl", "ru"))      // "nl"

And more request processing utilities.

Response Handling

  • Sending Response:
res.Status(201).Send("text")        // Text response
res.Status(200).Json(data)          // JSON response
res.Status(304).End()               // Empty response
  • Setting the Vary Response Header:
res.Vary("Accept-Encoding")         // Sets the `Vary` HTTP response header
  • Setting the Content-Type Response Header:
res.Type("application/json")        // Sets the `Content-Type` HTTP response header to the MIME type specified

And more response processing utilities.

Headers

req.Headers                         // All request headers (map[string]string)
res.Headers                         // All response headers (map[string][]string)
req.Header("Header-Name")           // Get request header
res.Header("Key", "Value")         // Set response header

Cookies

req.Cookies                     // All request cookies (map[string]string)
res.SetCookie(zttp.Cookie{      // Set response cookie
    Name: "session",
    Value: "token",
    Expires: time.Now().Add(24*time.Hour)
})

res.ClearCookie("username")	// Clear the username cookie

Static File Serving

res.Static("index.html", "./public")         // Serve HTML file
res.Static("image.png", "./assets")         // Serve image file

Middleware

// Global middleware
app.Use(func(req *zttp.Req, res *zttp.Res, next func()) {
    // Pre-processing
    next()
    // Post-processing
})

// Route-specific middleware
app.Use("/path", middlewareHandler)

Sub-Routers

router := app.NewRouter("/api/v1")
router.Get("/endpoint", handler)    // Handles /api/v1/endpoint
router.Use("/path", middlewareHandler) // Router-specific middleware

Cache Control

res.Header("ETag", "version1")
res.Header("Last-Modified", timestamp)

// If the request is still fresh in the client's cache
if req.Fresh() {
    // Handle cached responses, return 304 Not Changed response
    res.Status(304).End()
}

Error Handling

  • Automatic panic recovery
  • Manual error responses:
res.Status(400).Send("Bad request")

HTTPS Support via TLS

// Start secure server with TLS
app.StartTLS(443, "cert.pem", "key.pem")

For More details, visit the examples section.

Documentation

Index

Constants

View Source
const (
	// drwxr-xr-x
	DefaultDirPerm = os.ModeDir | 0755
	// -rw-------
	DefaultFilePerm = 0600
)

Variables

This section is empty.

Functions

This section is empty.

Types

type AcceptPart

type AcceptPart struct {
	// contains filtered or unexported fields
}

type App

type App struct {
	*Router
	Routers         []*Router
	PrettyPrintJSON bool
}

func NewApp

func NewApp() *App

New App constructor

func (*App) Delete

func (app *App) Delete(path string, handler Handler)

Register the passed handler and path with the app's delete routes

func (*App) Get

func (app *App) Get(path string, handler Handler)

Register the passed handler and path with the app's get routes

func (*App) NewRouter

func (app *App) NewRouter(path string) *Router

New Router constructor

func (*App) Patch

func (app *App) Patch(path string, handler Handler)

Register the passed handler and path with the app's patch routes

func (*App) Post

func (app *App) Post(path string, handler Handler)

Register the passed handler and path with the app's post routes

func (*App) Put

func (app *App) Put(path string, handler Handler)

Register the passed handler and path with the app's put routes

func (*App) Start

func (app *App) Start(port int)

Start listening to the given port

func (*App) StartTls

func (app *App) StartTls(port int, certFile, keyFile string)

Start listening securely to the given port

func (*App) Use

func (app *App) Use(args ...any)

Use registers a middleware function with the app Example1: app.Use(func(req *zttp.Req, res *zttp.Res, next) { ... }) Example2: app.Use("/admin", func(req *zttp.Req, res *zttp.Res, next) { ... }) Otherwise it will panic, don't panic her, please :)

type Cookie struct {
	Name        string    `json:"name"`
	Value       string    `json:"value"`
	Path        string    `json:"path"`
	Domain      string    `json:"domain"`
	Expires     time.Time `json:"expires"`
	MaxAge      int       `json:"max_age"`
	Secure      bool      `json:"secure"`
	HttpOnly    bool      `json:"http_only"`
	SameSite    string    `json:"same_site"`
	SessionOnly bool      `json:"session_only"`
}

type Ctx

type Ctx struct {
	Req *Req
	Res *Res
}

type FormFile

type FormFile struct {
	Filename string
	Content  []byte
	Header   textproto.MIMEHeader
}

type Handler

type Handler func(req *Req, res *Res)

type Middleware

type Middleware func(req *Req, res *Res, next func())

type MiddlewareWrapper

type MiddlewareWrapper struct {
	Path       string
	Middleware Middleware
}

MiddlewareWrapper wraps a Middleware with a certain path If the path is empty, the middleware will be applied globally to all requests If the path is set, the middleware will only be applied to matched requests

type Req

type Req struct {
	LocalAddress string
	Method       string
	Path         string
	Body         string
	Headers      map[string]string
	Params       map[string]string
	Queries      map[string]string
	Cookies      map[string]string
	*Ctx
}

func (*Req) Accepts

func (req *Req) Accepts(offered ...string) string

Checks if the specified types are accepted from the HTTP client TODO: Fix Canonicalization

func (*Req) AcceptsCharsets

func (req *Req) AcceptsCharsets(offered ...string) string

Checks if the specified types are accepted from the HTTP client

func (*Req) AcceptsEncodings

func (req *Req) AcceptsEncodings(offered ...string) string

func (*Req) AcceptsLanguages

func (req *Req) AcceptsLanguages(offered ...string) string

func (*Req) App

func (req *Req) App() *App

Return the reference to the app this request is associated with

func (*Req) FormFile

func (req *Req) FormFile(name string) (*FormFile, error)

Return the file of the specified part if the request is multipart

func (*Req) FormValue

func (req *Req) FormValue(key string) string

Return the value of the specified part if the request is multipart

func (*Req) Fresh

func (req *Req) Fresh() bool

Return true when the response is still “fresh” in the client's cache. otherwise false is returned to indicate that the client cache is now stale and the full response should be sent. When a client sends the Cache-Control: no-cache request header to indicate an end-to-end reload request, this will return false to make handling these requests transparent. This logic is heavily inspired by the official gofiber source code, with some touches of mine: https://github.com/gofiber/fiber/blob/main/ctx.go

func (*Req) Header

func (req *Req) Header(key string) string

Return the value of the passed header key

func (*Req) Host

func (req *Req) Host() string

Return the base URL of the request derived from the `Host` HTTP header TODO: Should check the `X-Forwarded-Host` HTTP header also

func (*Req) Hostname

func (req *Req) Hostname() string

TODO: Align with RFC 7230 standards TODO: Handle IPv4 and IPv6 hosts

func (*Req) IP

func (req *Req) IP() string

TODO: Align with RFC 7239 standards

func (*Req) Param

func (req *Req) Param(key string) string

Return the value of the passed param key

func (*Req) ParseJson

func (req *Req) ParseJson(target any) error

Parse the request body into the target struct Note that the target MUST be a pointer

func (*Req) Query

func (req *Req) Query(key string) string

Return the value of the passed query key

func (*Req) Save

func (req *Req) Save(formFile *FormFile, destination string) error

Save the multipart form file directly to disk TODO: I think permissions should be a config later

func (*Req) Stale

func (req *Req) Stale() bool

If the request is not fresh, then it's stale

type Res

type Res struct {
	Socket          net.Conn
	StatusCode      int
	Headers         map[string][]string
	ContentType     string
	PrettyPrintJSON bool
	*Ctx
}

func (*Res) ClearCookie

func (res *Res) ClearCookie(key ...string)

Clear the specified client cookies If no keys are specified, all client cookies are cleared

func (*Res) End

func (res *Res) End()

This function ends the current response

func (*Res) Header

func (res *Res) Header(key, value string) *Res

Sets the value of the passed header key

func (*Res) Json

func (res *Res) Json(data any)

This function sends a JSON response body

func (*Res) Send

func (res *Res) Send(data string)

This function sends a text/plain response body

func (*Res) SetCookie

func (res *Res) SetCookie(cookie Cookie) *Res

Sets the response cookies

func (*Res) Static

func (res *Res) Static(path, root string)

func (*Res) Status

func (res *Res) Status(code int) *Res

Sets the status code of the current response

func (*Res) Type

func (res *Res) Type(contentType string) *Res

Sets the `Content-Type` HTTP response header to the MIME type specified TODO: Add support for setting the charset

func (*Res) Vary

func (res *Res) Vary(fields ...string)

Sets the `Vary` HTTP response header

type Route

type Route struct {
	// contains filtered or unexported fields
}

type Router

type Router struct {
	*App
	// contains filtered or unexported fields
}

func (*Router) Delete

func (router *Router) Delete(path string, handler Handler)

Register the passed handler and path with the router's delete routes

func (*Router) Get

func (router *Router) Get(path string, handler Handler)

Register the passed handler and path with the router's get routes

func (*Router) Patch

func (router *Router) Patch(path string, handler Handler)

Register the passed handler and path with the router's patch routes

func (*Router) Post

func (router *Router) Post(path string, handler Handler)

Register the passed handler and path with the router's post routes

func (*Router) Put

func (router *Router) Put(path string, handler Handler)

Register the passed handler and path with the router's put routes

func (*Router) Use

func (router *Router) Use(args ...any)

Use registers a middleware function with the router Example1: router.Use(func(req *zttp.Req, res *zttp.Res, next) { ... }) Example2: router.Use("/admin", func(req *zttp.Req, res *zttp.Res, next) { ... }) Otherwise it will panic, don't panic her, please :)

Directories

Path Synopsis
examples
tls

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL