Introduction to fasthttp

When it comes to building high-performance HTTP servers in Go, the fasthttp package is often the go-to choice for developers who need to handle thousands of requests per second with minimal latency. In this article, we’ll delve into the world of fasthttp, exploring its features, how it compares to the standard net/http package, and most importantly, how to use it to build a blazing fast HTTP server.

Why fasthttp?

fasthttp is designed for high-performance edge cases where the standard net/http package might not suffice. Here are some key reasons why you might prefer fasthttp:

  • Speed: fasthttp is optimized for speed and can handle more than 100K requests per second (qps) and over 1 million concurrent keep-alive connections on modern hardware[2][4].
  • Low Memory Allocation: Unlike net/http, which uses significant heap allocation, fasthttp minimizes memory allocations in hot paths, making it much faster in raw performance[3].
  • Zero Memory Allocations: In many benchmarks, fasthttp shows zero memory allocations in its hot paths, which is a significant performance booster[4].

Setting Up Your Environment

Before diving into the code, ensure you have the latest version of Go installed. fasthttp works seamlessly with Go version 1.22 or higher.

go get -u github.com/valyala/fasthttp

Basic fasthttp Server

Let’s start with a simple example of a fasthttp server. Here’s how you can set up a basic server that responds to GET requests:

package main

import (
    "log"
    "github.com/valyala/fasthttp"
)

func main() {
    handler := func(ctx *fasthttp.RequestCtx) {
        switch string(ctx.Path()) {
        case "/":
            ctx.SetContentType("text/plain; charset=utf-8")
            ctx.SetBody([]byte("Welcome to the fasthttp server"))
        default:
            ctx.Error("Unsupported path", fasthttp.StatusNotFound)
        }
    }

    if err := fasthttp.ListenAndServe(":8080", handler); err != nil {
        log.Fatalf("Error in ListenAndServe: %s", err)
    }
}

This example sets up a server listening on port 8080 and responds with a plain text message when the root path is accessed.

Handling Requests and Responses

One of the key differences between fasthttp and net/http is the way you handle requests and responses. Here’s a comparison of how you might handle a simple request in both packages:

net/http Example

package main

import (
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
    case "/":
        w.Write([]byte("Welcome to the net/http server"))
    default:
        http.Error(w, "Unsupported path", http.StatusNotFound)
    }
}

func main() {
    http.HandleFunc("/", handler)
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

fasthttp Example

package main

import (
    "github.com/valyala/fasthttp"
)

func handler(ctx *fasthttp.RequestCtx) {
    switch string(ctx.Path()) {
    case "/":
        ctx.SetContentType("text/plain; charset=utf-8")
        ctx.SetBody([]byte("Welcome to the fasthttp server"))
    default:
        ctx.Error("Unsupported path", fasthttp.StatusNotFound)
    }
}

func main() {
    if err := fasthttp.ListenAndServe(":8080", handler); err != nil {
        panic(err)
    }
}

Notice the difference in how the request context is handled. In fasthttp, the RequestCtx object provides all the necessary functionality for processing requests and writing responses.

Testing Your fasthttp Server

Testing is a crucial part of any development process. Here’s how you can test your fasthttp server using in-memory listeners, which significantly improve test performance.

Using fasthttputil.NewInmemoryListener

package main

import (
    "testing"
    "github.com/valyala/fasthttp"
    "github.com/valyala/fasthttp/fasthttputil"
    "github.com/valyala/fasthttp/testing"
)

func TestHandler(t *testing.T) {
    ln := fasthttputil.NewInmemoryListener()
    defer ln.Close()

    go func() {
        if err := fasthttp.ListenAndServe(ln, handler); err != nil {
            t.Errorf("ListenAndServe error: %v", err)
        }
    }()

    req := testing.DoRequest(ln, "GET", "/", nil)
    if req.StatusCode != fasthttp.StatusOK {
        t.Errorf("Expected status code %d but got %d", fasthttp.StatusOK, req.StatusCode)
    }
}

This approach avoids the need to listen on a real network port, making your tests much faster.

Performance Comparison

To understand the performance difference between net/http and fasthttp, let’s look at some benchmark results.

Benchmark Results

Here are some benchmark results comparing net/http and fasthttp:

net/http Benchmarks

BenchmarkNetHTTPServerGet10ReqPerConn-4 500000 2751 ns/op 2118 B/op 19 allocs/op
BenchmarkNetHTTPServerGet100ReqPerConn-4 500000 2398 ns/op 2037 B/op 18 allocs/op

fasthttp Benchmarks

BenchmarkServerGet10ReqPerConn-4 3000000 417 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet100ReqPerConn-4 5000000 351 ns/op 0 B/op 0 allocs/op

As you can see, fasthttp outperforms net/http significantly, especially in terms of memory allocations and execution time[2][4].

Sequence Diagram for fasthttp Request Handling

Here is a sequence diagram illustrating how a request is handled in a fasthttp server:

sequenceDiagram participant Client participant Server participant Handler Note over Client,Server: Client sends a GET request Client->>Server: GET / HTTP/1.1 Server->>Handler: Call handler function Handler->>Server: Process request and set response Server->>Client: HTTP/1.1 200 OK

Conclusion

Building a high-performance HTTP server in Go using fasthttp is a straightforward and rewarding process. With its optimized design for speed and minimal memory allocations, fasthttp is the perfect choice for applications that require handling thousands of requests per second.

By following the examples and best practices outlined in this article, you can create a blazing fast HTTP server that meets the demands of your high-traffic applications. Remember, when it comes to performance, every millisecond counts, and fasthttp is here to help you shave off those precious milliseconds. Happy coding