Claude Code Plugins

Community-maintained marketplace

Feedback

rweb-light-go-webserver

@rohanthewiz/rweb
2
0

Build HTTP web servers with the light and low-dependency RWeb Go framework. Covers routing, middleware, cookies, groups, SSE, WebSockets, static files, proxying, and file uploads.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name rweb-light-go-webserver
description Build HTTP web servers with the light and low-dependency RWeb Go framework. Covers routing, middleware, cookies, groups, SSE, WebSockets, static files, proxying, and file uploads.

RWeb Framework Skill

RWeb is a high-performance, lightweight HTTP web server framework for Go featuring a custom radix tree router and practically zero third party dependencies.

Getting Started

Basic Server Setup

package main

import (
    "log"
    "github.com/rohanthewiz/rweb"
)

func main() {
    s := rweb.NewServer(rweb.ServerOptions{
        Address: ":8080",  // Use ":port" format for Docker compatibility
        Verbose: true,     // Enable request logging
        Debug:   false,    // Debug mode
    })

    s.Get("/", func(ctx rweb.Context) error {
        return ctx.WriteString("Hello, World!")
    })

    log.Fatal(s.Run())
}

TLS/HTTPS Configuration

s := rweb.NewServer(rweb.ServerOptions{
    Address: ":443",
    TLS: rweb.TLSCfg{
        UseTLS:   true,
        KeyFile:  "certs/localhost.key",
        CertFile: "certs/localhost.crt",
    },
})

Routing

HTTP Methods

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

Route Parameters

// Access via ctx.Request().PathParam("name")
s.Get("/users/:id", func(ctx rweb.Context) error {
    id := ctx.Request().PathParam("id")
    return ctx.WriteString("User ID: " + id)
})

// Nested parameters
s.Get("/orgs/:org/repos/:repo", func(ctx rweb.Context) error {
    org := ctx.Request().PathParam("org")
    repo := ctx.Request().PathParam("repo")
    return ctx.WriteJSON(map[string]string{"org": org, "repo": repo})
})

Fixed vs Parameterized Routes

Fixed routes take precedence over parameterized ones when there's an exact match:

s.Get("/greet/:name", handler)    // Matches /greet/john, /greet/mary
s.Get("/greet/city", handler)     // Exact match takes priority for /greet/city

Response Types

// Plain text
ctx.WriteString("Hello")

// HTML
ctx.WriteHTML("<h1>Welcome</h1>")

// JSON (auto-marshals Go structs/maps)
ctx.WriteJSON(map[string]string{"status": "ok"})

// CSS helper
rweb.CSS(ctx, "body { color: red; }")

// File download
rweb.File(ctx, "filename.txt", fileBytes)

// Set status code
ctx.SetStatus(404).WriteString("Not found")

// Redirect
ctx.Redirect(302, "/new-location")

Middleware

Middleware executes in registration order. Call ctx.Next() to continue the chain.

// Global middleware
s.Use(func(ctx rweb.Context) error {
    start := time.Now()
    defer func() {
        fmt.Printf("%s %s -> %d [%s]\n",
            ctx.Request().Method(),
            ctx.Request().Path(),
            ctx.Response().Status(),
            time.Since(start))
    }()
    return ctx.Next()
})

// Built-in request info logger -- highly recommended
s.Use(rweb.RequestInfo)

Context Data Storage

Store request-scoped data accessible to all middleware and handlers:

// Set data (typically in middleware)
ctx.Set("userId", "123")
ctx.Set("isAdmin", true)

// Get data
userId := ctx.Get("userId").(string)

// Check existence
if ctx.Has("isLoggedIn") {
    // user is logged in
}

// Delete data
ctx.Delete("userId")

Authentication Pattern

s.Use(func(ctx rweb.Context) error {
    authHeader := ctx.Request().Header("Authorization")
    if authHeader == "Bearer valid-token" {
        ctx.Set("isLoggedIn", true)
        ctx.Set("userId", "123")
    }
    return ctx.Next()
})

s.Get("/profile", func(ctx rweb.Context) error {
    if !ctx.Has("isLoggedIn") || !ctx.Get("isLoggedIn").(bool) {
        return ctx.SetStatus(401).WriteString("Unauthorized")
    }
    return ctx.WriteJSON(map[string]string{"userId": ctx.Get("userId").(string)})
})

Route Groups

Organize routes with common prefixes and middleware:

// API versioning
api := s.Group("/api")
v1 := api.Group("/v1")
v1.Get("/status", statusHandler)  // GET /api/v1/status

// Protected routes with middleware
users := v1.Group("/users", authMiddleware)
users.Get("/", listUsers)         // GET /api/v1/users
users.Get("/:id", getUser)        // GET /api/v1/users/:id
users.Post("/", createUser)       // POST /api/v1/users

// Multiple middleware (executed in order)
admin := s.Group("/admin", authMiddleware, adminMiddleware)
admin.Get("/dashboard", dashboardHandler)

Cookies

Server-wide Cookie Config

s := rweb.NewServer(rweb.ServerOptions{
    Address: ":8080",
    Cookie: rweb.CookieConfig{
        HttpOnly: true,
        SameSite: rweb.SameSiteLaxMode,
        Path:     "/",
    },
})

Cookie Operations

// Set simple cookie
ctx.SetCookie("name", "value")

// Set cookie with options
cookie := &rweb.Cookie{
    Name:    "session_id",
    Value:   sessionID,
    Expires: time.Now().Add(30 * 24 * time.Hour),
    MaxAge:  30 * 24 * 60 * 60,
}
ctx.SetCookieWithOptions(cookie)

// Get cookie
value, err := ctx.GetCookie("name")

// Check if cookie exists
if ctx.HasCookie("session_id") {
    // cookie exists
}

// Delete cookie
ctx.DeleteCookie("name")

// Get and clear (useful for flash messages)
value, err := ctx.GetCookieAndClear("flash")

Static Files

// StaticFiles(urlPrefix, localPath, stripPrefixSegments)

// /static/images/photo.png -> ./assets/images/photo.png
s.StaticFiles("/static/images/", "./assets/images", 2)

// /css/style.css -> ./assets/css/style.css
s.StaticFiles("/css/", "./assets/css", 1)

// /.well-known/acme -> ./.well-known/acme
s.StaticFiles("/.well-known/", "./", 0)

File Uploads

s.Post("/upload", func(ctx rweb.Context) error {
    req := ctx.Request()

    // Get form field
    name := req.FormValue("name")

    // Get uploaded file
    file, header, err := req.GetFormFile("file")
    if err != nil {
        return err
    }
    defer file.Close()

    // Read file content
    data, err := io.ReadAll(file)
    if err != nil {
        return err
    }

    // Save to disk
    return os.WriteFile("uploads/"+header.Filename, data, 0666)
})

Server-Sent Events (SSE)

// Create event channel
eventsChan := make(chan any, 100)

// Option 1: Using SetupSSE
s.Get("/events", func(ctx rweb.Context) error {
    return s.SetupSSE(ctx, eventsChan)
})

// Option 2: Using SSEHandler helper
s.Get("/events2", s.SSEHandler(eventsChan))

// Send events from anywhere
eventsChan <- "event data"
eventsChan <- map[string]string{"type": "update", "data": "value"}

WebSockets

s.WebSocket("/ws/echo", func(ws *rweb.WSConn) error {
    defer ws.Close(1000, "Closing")

    // Send message
    ws.WriteMessage(rweb.TextMessage, []byte("Welcome"))

    // Read loop
    for {
        msg, err := ws.ReadMessage()
        if err != nil {
            break
        }

        switch msg.Type {
        case rweb.TextMessage:
            ws.WriteMessage(rweb.TextMessage, msg.Data)
        case rweb.BinaryMessage:
            ws.WriteMessage(rweb.BinaryMessage, msg.Data)
        case rweb.CloseMessage:
            return nil
        }
    }
    return nil
})

// Ping/Pong for keepalive
ws.SetPongHandler(func(data []byte) error {
    return nil
})
ws.WritePing([]byte("ping"))

Reverse Proxy

// Proxy(urlPrefix, targetURL, stripPrefixSegments)

// /api/backend/* -> http://backend:8081/*
err := s.Proxy("/api/backend", "http://backend:8081", 2)
if err != nil {
    log.Fatal(err)
}

Request Information

req := ctx.Request()

req.Method()              // GET, POST, etc.
req.Path()                // /users/123
req.PathParam("id")       // Route parameter
req.Header("Authorization") // Request header
req.Body()                // Raw request body bytes
req.FormValue("field")    // Form field value
req.GetPostValue("field") // POST form value

Handler Signature

All handlers follow this signature:

func(ctx rweb.Context) error

Return nil for success, or an error which rweb will handle appropriately.

Testing Endpoints

# Basic GET
curl http://localhost:8080/

# With headers
curl -H "Authorization: Bearer token" http://localhost:8080/api/users

# POST with form data
curl -X POST -d "name=John" http://localhost:8080/users

# File upload
curl -X POST -F "file=@document.pdf" http://localhost:8080/upload

# JSON body
curl -X POST -H "Content-Type: application/json" -d '{"name":"John"}' http://localhost:8080/users