RepoPilotOpen in app →

gobwas/ws

A tiny WebSocket library for Go.

Healthy

Healthy across the board

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 3mo ago
  • 17 active contributors
  • MIT licensed
Show all 6 evidence items →
  • CI configured
  • Tests present
  • Concentrated ownership — top contributor handles 56% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/gobwas/ws)](https://repopilot.app/r/gobwas/ws)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/gobwas/ws on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: gobwas/ws

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/gobwas/ws shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across the board

  • Last commit 3mo ago
  • 17 active contributors
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Concentrated ownership — top contributor handles 56% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live gobwas/ws repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/gobwas/ws.

What it runs against: a local clone of gobwas/ws — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in gobwas/ws | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 114 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>gobwas/ws</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of gobwas/ws. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/gobwas/ws.git
#   cd ws
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of gobwas/ws and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "gobwas/ws(\\.git)?\\b" \\
  && ok "origin remote is gobwas/ws" \\
  || miss "origin remote is not gobwas/ws (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "frame.go" \\
  && ok "frame.go" \\
  || miss "missing critical file: frame.go"
test -f "server.go" \\
  && ok "server.go" \\
  || miss "missing critical file: server.go"
test -f "dialer.go" \\
  && ok "dialer.go" \\
  || miss "missing critical file: dialer.go"
test -f "read.go" \\
  && ok "read.go" \\
  || miss "missing critical file: read.go"
test -f "write.go" \\
  && ok "write.go" \\
  || miss "missing critical file: write.go"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 114 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~84d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/gobwas/ws"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

gobwas/ws is a minimal, zero-copy RFC6455 WebSocket implementation for Go that prioritizes memory efficiency and low-level control. Unlike higher-level libraries, it exposes frame-by-frame I/O primitives (read.go, write.go, frame.go) allowing users to reuse buffers across connections and build custom packet-handling logic, while also providing wsutil helpers for faster adoption. Flat single-package structure: core protocol logic in root (frame.go, read.go, write.go, cipher.go, server.go, dialer.go, http.go), utility functions separated into util.go/util_unsafe.go/util_purego.go, version-specific implementations for Go 1.17–1.20 (dialer_tls_go17.go, hijack_go119.go, hijack_go120.go), tests colocated as *_test.go, examples in example/ directory, and autobahn/ for integration testing against the official test suite.

👥Who it's for

Go backend engineers building low-latency WebSocket servers or clients who need to handle thousands of concurrent connections without allocating intermediate buffers, and teams that need RFC6455 compliance validated against the Autobahn Test Suite.

🌱Maturity & risk

Production-ready and stable. The library is tagged v1* (semantic versioning enforced), passes the Autobahn Test Suite (78% coverage reported in README), has CI/CD via GitHub Actions (.github/workflows/main.yml, autobahn.yml), and depends only on two tiny maintained packages (gobwas/httphead, gobwas/pool). Last activity and test presence indicate active maintenance.

Very low risk for a well-scoped library. Minimal dependency tree (httphead, pool, golang.org/x/sys only), strong backward-compatibility guarantee (v1* tag), and comprehensive test coverage (cipher_test.go, dialer_test.go, frame_test.go, read_test.go, write_test.go, http_test.go, server_test.go). Single-maintainer concern mitigated by stable API and passing test suite.

Active areas of work

No specific recent changes visible in the file list, but the presence of version-specific files (hijack_go119.go, hijack_go120.go, main_go17.go/go18.go) and Go 1.16+ in go.mod indicates ongoing maintenance for Go compatibility. The Autobahn integration tests suggest continuous validation.

🚀Get running

Check README for instructions.

Daily commands: Run example echo server: go run example/autobahn/autobahn.go (if present and functional). Run unit tests: go test ./.... Run Autobahn test suite: cd autobahn && bash script/test.sh (requires Docker). No dev server needed—this is a library; usage is via import "github.com/gobwas/ws".

🗺️Map of the codebase

  • frame.go — Core frame parsing and writing logic; implements the RFC6455 frame format that all WebSocket communication relies on
  • server.go — Server-side upgrade and connection handling; entry point for accepting WebSocket connections from HTTP
  • dialer.go — Client-side connection establishment; implements the WebSocket handshake and dial logic
  • read.go — Low-level frame reading from connections; handles masking, payload extraction, and buffering strategies
  • write.go — Low-level frame writing to connections; handles frame encoding, masking for clients, and payload delivery
  • wsutil/handler.go — High-level message handler wrapper; simplifies common patterns of reading/writing complete messages
  • check.go — Validation utilities for frame structure and protocol compliance; prevents malformed frames from propagating

🧩Components & responsibilities

  • Frame (frame.go) (RFC6455) — Represents a single WebSocket frame: header, opcode, payload, FIN bit. Immutable data structure.

🛠️How to make changes

Add a new low-level frame reader option

  1. Define new option type in read.go as a function that modifies Reader config (read.go)
  2. Implement the option function to set Reader fields (e.g., buffer size, masking behavior) (read.go)
  3. Update Reader struct to include new field if needed (frame.go)
  4. Add tests using the new option in read_test.go (read_test.go)

Implement a custom WebSocket extension handler

  1. Study wsflate/extension.go to understand Extension interface (wsflate/extension.go)
  2. Create new file in wsflate/ or at package root implementing Extension interface (Reserve, Negotiate, etc.) (wsflate/extension.go)
  3. Register extension in server.go's Upgrade or dialer.go's Dial via Extensions option (server.go)
  4. Add round-trip tests verifying negotiation and frame transformation (tests/deflate_test.go)

Add custom message framing logic to wsutil

  1. Create new file in wsutil/ (e.g., wsutil/custom_handler.go) (wsutil/handler.go)
  2. Wrap low-level Reader/Writer from read.go and write.go to implement your message protocol (wsutil/reader.go)
  3. Expose public functions matching wsutil patterns (e.g., ReadMessage, WriteMessage variants) (wsutil/wsutil.go)
  4. Add comprehensive tests in wsutil/custom_handler_test.go (wsutil/handler_test.go)

🔧Why these technologies

  • Go + syscall optimizations (unsafe, cgo-free) — Enables zero-copy I/O on Windows/Linux; frame reads bypass allocations via pointer tricks in util_unsafe.go
  • RFC6455 frame-level API — Allows users to manage buffer reuse across connections and build custom framing on top of protocol primitives
  • Separate wsutil high-level package — Offers two APIs: low-level (frame.go/read.go/write.go) for power users, high-level (wsutil) for convenience
  • Per-message deflate (wsflate) as pluggable extension — Compression is optional via Extension interface, not forced; users control bandwidth vs CPU tradeoff

⚖️Trade-offs already made

  • Low-level Reader/Writer API exposes frame-at-a-time instead of message-at-a-time

    • Why: Enables zero-copy and buffer reuse; user handles frame assembly and reassembly
    • Consequence: Higher learning curve; wsutil package required for typical use. But power users can optimize buffer pools
  • No goroutine-per-connection by default; caller controls concurrency model

    • Why: Library stays agnostic to user's architecture (goroutines, async, event loops)
    • Consequence: Users must implement their own read/write loop or use wsutil helpers; adds responsibility
  • Masking handled separately in cipher.go, not embedded in frame encoding

    • Why: Allows reuse of masking logic and custom implementations (e.g., pool-based key generation)
    • Consequence: Client code must explicitly call masking; more boilerplate but clearer separation of concerns
  • No connection pooling or keep-alive built-in

    • Why: Library targets protocol layer; keep-alive is application policy (e.g., ping/pong frames sent by user)
    • Consequence: Users must implement ping/pong handlers and reconnection logic in wsutil/handler or custom code

🚫Non-goals (don't propose these)

  • Authentication and authorization (delegated to HTTP upgrade handshake layer)
  • Automatic reconnection or connection pooling
  • Built-in logging or metrics (user-supplied via context or callbacks)
  • gRPC-over-WebSocket or other RPC frameworks
  • HTTP/2 push or other HTTP features beyond upgrade negotiation

🪤Traps & gotchas

Version-specific compilation: Go version 1.17–1.20+ behavior differs (hijack_go119.go vs hijack_go120.go, dialer_tls_go17.go vs dialer_tls_go18.go). Unsafe code in util_unsafe.go assumes little-endian platforms and specific memory layout; util_purego.go provides fallback but is slower. Autobahn testing requires Docker. Buffer pooling (via gobwas/pool) means reused buffers must not be accessed after being returned. Masking is mandatory for client→server frames (RFC6455); not handling this correctly causes protocol violations.

🏗️Architecture

💡Concepts to learn

  • RFC6455 WebSocket Protocol — This library is a direct implementation of RFC6455; understanding the spec (frame format, handshake, masking rules, opcodes) is essential to reading frame.go, server.go, and dialer.go
  • Zero-Copy I/O — The entire value proposition of ws is zero-copy frame reading/writing; understanding how read.go and write.go avoid allocations requires knowledge of buffer pooling and direct memory access patterns
  • WebSocket Masking — RFC6455 requires client-to-server frames to be XOR-masked for security; cipher.go implements this; incorrect masking causes protocol violations detectable by Autobahn tests
  • Frame-Based Streaming — Unlike HTTP request/response, WebSocket frames are bidirectional and interleaved; frame.go defines the wire format and read.go/write.go implement streaming logic that handles partial reads/writes
  • HTTP Upgrade Mechanism — WebSocket begins as an HTTP upgrade request; server.go and dialer.go handle the handshake (Sec-WebSocket-Key, Sec-WebSocket-Accept) before switching to frame mode
  • DEFLATE Compression (Permessage Deflate) — Optional WebSocket extension; wsflate/ directory provides compression; understanding when/how to enable it affects frame reading and message reassembly
  • Unsafe Pointer Operations — util_unsafe.go uses unsafe casts for fast byte slicing and integer conversion; critical for understanding zero-copy performance but requires careful code review and platform assumptions
  • gorilla/websocket — Most popular Go WebSocket library; higher-level than ws but less control over buffer reuse
  • nhooyr/websocket — Modern Go WebSocket implementation with similar zero-copy goals; good alternative to compare design decisions
  • gobwas/httphead — Direct dependency of ws; provides low-level HTTP header parsing used in WebSocket handshake
  • gobwas/pool — Direct dependency of ws; provides buffer pooling that enables zero-copy I/O across connections
  • gobwas/ws-examples — Companion repo with real-world example applications (echo, chat, etc.) using the ws library

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive benchmarks for read/write operations

The repo emphasizes 'zero-copy upgrade' and 'no intermediate allocations during I/O' as core features, but there are no benchmark files (e.g., *_bench.go) in read.go, write.go, cipher.go, or frame.go. Adding benchmarks would validate performance claims and help catch regressions. This is critical for a library focused on performance optimization.

  • [ ] Create read_bench_test.go with benchmarks for ReadFrame, ReadHeader operations
  • [ ] Create write_bench_test.go with benchmarks for WriteFrame, WriteHeader operations
  • [ ] Create frame_bench_test.go benchmarking frame parsing logic
  • [ ] Create cipher_bench_test.go for masking/unmasking performance
  • [ ] Update Makefile with bench target to run all benchmarks consistently
  • [ ] Add benchmark results to README.md for transparency

Expand wsutil test coverage with real connection scenarios

The wsutil package (helper utilities for high-level usage) has basic test files (handler_test.go, dialer_test.go, cipher_test.go) but wsutil/extenstion.go appears to lack tests entirely. Additionally, wsutil/handler.go likely needs integration tests covering common patterns (echo handlers, message broadcasting). This is the primary entry point for new users.

  • [ ] Create wsutil/extension_test.go with tests for extension negotiation and handling
  • [ ] Expand wsutil/handler_test.go with integration tests for read/write message loops
  • [ ] Add test for wsutil/handler.go covering concurrent message handling scenarios
  • [ ] Add test cases for error conditions (connection drops, protocol violations)
  • [ ] Reference specific examples from example/autobahn/autobahn.go in tests

Add CI workflow for wsflate compression extension validation

The wsflate package contains a complete permessage-deflate implementation with tests, but there's no dedicated CI workflow validating it against the Autobahn Test Suite (unlike the main autobahn.yml which tests core protocol). The .github/workflows/autobahn.yml should be extended to test compression scenarios separately, or a new workflow should validate wsflate compliance.

  • [ ] Review autobahn/config/fuzzingclient.json to identify compression test cases
  • [ ] Create .github/workflows/wsflate-compression.yml to run Autobahn tests with wsflate enabled
  • [ ] Configure autobahn/docker/server/Dockerfile to include compression extension in test server
  • [ ] Add compression-specific test matrix (different compression levels, window sizes)
  • [ ] Document in README.md which Autobahn compression tests pass

🌿Good first issues

  • Add benchmarks for read.go and write.go tight loops to establish performance baselines and catch regressions; currently no *_bench.go files present
  • Expand tests/deflate_test.go coverage for wsflate/ subpackage; check if all code paths in compression/decompression are exercised
  • Document the buffer reuse pattern in example code (example/autobahn/ or new examples/) showing how to use gobwas/pool with ws.Reader/ws.Writer correctly

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 6ee0554 — Allow buffer reuse in ReadHeader/WriteHeader variants. (#219) (iamtakingiteasy)
  • 36a216a — ci: bump actions/upload-artifact from 4 to 5 (#213) (dependabot[bot])
  • da6b093 — ci: bump actions/setup-go from 5 to 6 (#212) (dependabot[bot])
  • 0e2f65f — ci: bump actions/checkout from 4 to 5 (#211) (dependabot[bot])
  • 4f03007 — Extract Autobahn test suite to a separate CI step (#199) (cristaloleg)
  • af7917f — Cipher 1.9x speedup (#198) (klauspost)
  • 02bca95 — Drop Go 1.15 support (#191) (cristaloleg)
  • 4600238 — Add Dialer.Host field (#196) (cristaloleg)
  • c7418fd — ci: bump actions/upload-artifact from 3 to 4 (#193) (dependabot[bot])
  • 40bc1fc — ci: bump actions/setup-go from 4 to 5 (#192) (dependabot[bot])

🔒Security observations

The gobwas/ws WebSocket library demonstrates generally good security practices with a focus on RFC6455 compliance and proper testing. However, the codebase uses an outdated Go version (1.16) which is no longer actively supported, posing the most significant risk. Dependency version management lacks explicit pinning of direct dependencies. The presence of multiple version-specific code paths may create maintenance gaps. No documented security policy is visible for responsible vulnerability disclosure. Recommendations focus on modernizing the Go version requirement, implementing explicit dependency versioning, and establishing clear security communication channels.

  • Medium · Outdated Go Version Requirement — go.mod. The module requires Go 1.16, which is significantly outdated (released February 2021). Go 1.16 is no longer supported and may contain known security vulnerabilities. Current stable Go versions include 1.21+. Fix: Update go.mod to require Go 1.19 or later (1.21+ preferred). Review and update all dependencies to versions compatible with modern Go releases.
  • Medium · Transitive Dependency with Minimal Version Pinning — go.mod. The go.mod file shows golang.org/x/sys v0.6.0 as an indirect dependency but lacks explicit version pinning for direct dependencies (github.com/gobwas/httphead and github.com/gobwas/pool). Without explicit versions in go.mod, builds may pull different versions over time. Fix: Explicitly pin all direct dependencies with specific versions in go.mod. Use 'go get -u' to update to latest secure versions and then audit them. Ensure go.sum is properly maintained and committed.
  • Low · Multiple Go Version-Specific Build Files — Multiple files with _go*.go suffix. The codebase contains version-specific files (dialer_tls_go17.go, dialer_tls_go18.go, main_go17.go, main_go18.go, hijack_go119.go, hijack_go120.go) that may indicate inconsistent handling across Go versions. This pattern can lead to missed security patches if one version path receives updates and others don't. Fix: Consolidate version-specific code paths where possible. Establish a clear minimum Go version support policy and drop support for versions older than 3-4 releases from current stable. Document version-specific behavior clearly.
  • Low · Missing Security Policy Documentation — Repository root. No SECURITY.md or security policy file is evident in the file structure, making it unclear how security vulnerabilities should be reported. Fix: Create a SECURITY.md file following GitHub's security policy template. Include responsible disclosure process and contact information for security researchers.
  • Low · Autobahn Test Configuration May Expose Test Infrastructure — autobahn/docker/autobahn/Dockerfile, autobahn/docker/server/Dockerfile. Docker configurations for Autobahn testing (autobahn/docker/) are present in the repository. While these appear to be for testing, ensure they are not accidentally deployed to production and that test endpoints are not exposed publicly. Fix: Ensure Docker test configurations are excluded from production deployments. Document that these are test-only. Consider moving test infrastructure to a separate testing repository or CI-only pipeline.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · gobwas/ws — RepoPilot