This commit is contained in:
+6
-1
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Unreleased
|
## v0.1.0-preview.8 — 2026-08-28
|
||||||
|
|
||||||
|
- Preserve `http.Hijacker` through the request-evidence middleware so audited,
|
||||||
|
authenticated WebSocket and other HTTP upgrade handlers can operate without
|
||||||
|
bypassing request logging. Successful upgrades are recorded as HTTP 101;
|
||||||
|
upgraded-protocol bytes remain outside HTTP body-byte accounting.
|
||||||
|
|
||||||
- Add revisioned active/archived lifecycles for organizations and teams,
|
- Add revisioned active/archived lifecycles for organizations and teams,
|
||||||
invitation listing and revocation, membership suspension/removal, team-member
|
invitation listing and revocation, membership suspension/removal, team-member
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Gamertan Web Foundations
|
# Gamertan Web Foundations
|
||||||
|
|
||||||
> Status: `v0.1.0-preview.7` public preview. APIs may change before a stable
|
> Status: `v0.1.0-preview.8` public preview. APIs may change before a stable
|
||||||
> release; Linux is the maintained release platform.
|
> release; Linux is the maintained release platform.
|
||||||
|
|
||||||
Small, composable Go packages for the unglamorous boundaries of a careful web
|
Small, composable Go packages for the unglamorous boundaries of a careful web
|
||||||
@@ -24,14 +24,14 @@ Pin the preview in an application module, then import only the packages that
|
|||||||
application needs:
|
application needs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get gamertan.com/web@v0.1.0-preview.7
|
go get gamertan.com/web@v0.1.0-preview.8
|
||||||
go mod verify
|
go mod verify
|
||||||
```
|
```
|
||||||
|
|
||||||
An application may also name the first package it intends to adopt:
|
An application may also name the first package it intends to adopt:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get gamertan.com/web/requestmeta@v0.1.0-preview.7
|
go get gamertan.com/web/requestmeta@v0.1.0-preview.8
|
||||||
```
|
```
|
||||||
|
|
||||||
The version belongs to the `gamertan.com/web` module. Go compiles and links
|
The version belongs to the `gamertan.com/web` module. Go compiles and links
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
package requestlog
|
package requestlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -208,3 +210,17 @@ func (capture *responseCapture) Write(body []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (capture *responseCapture) Unwrap() http.ResponseWriter { return capture.ResponseWriter }
|
func (capture *responseCapture) Unwrap() http.ResponseWriter { return capture.ResponseWriter }
|
||||||
|
|
||||||
|
// Hijack preserves connection-upgrade support through the request evidence
|
||||||
|
// wrapper. A successful upgrade is recorded as HTTP 101; bytes exchanged after
|
||||||
|
// hijacking belong to the upgraded protocol and are intentionally not counted
|
||||||
|
// as HTTP response-body bytes.
|
||||||
|
func (capture *responseCapture) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
connection, buffer, err := http.NewResponseController(capture.ResponseWriter).Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
capture.wroteHeader = true
|
||||||
|
capture.status = http.StatusSwitchingProtocols
|
||||||
|
return connection, buffer, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
package requestlog
|
package requestlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -24,6 +26,16 @@ type memorySink struct {
|
|||||||
ctxErr error
|
ctxErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type hijackableRecorder struct {
|
||||||
|
*httptest.ResponseRecorder
|
||||||
|
connection net.Conn
|
||||||
|
buffer *bufio.ReadWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (recorder *hijackableRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
return recorder.connection, recorder.buffer, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sink *memorySink) WriteRecord(ctx context.Context, record Record) error {
|
func (sink *memorySink) WriteRecord(ctx context.Context, record Record) error {
|
||||||
sink.records = append(sink.records, record)
|
sink.records = append(sink.records, record)
|
||||||
sink.ctxErr = ctx.Err()
|
sink.ctxErr = ctx.Err()
|
||||||
@@ -208,3 +220,29 @@ func TestResponseStatusUsesFirstHeader(t *testing.T) {
|
|||||||
t.Fatalf("status=%d", sink.records[0].Status)
|
t.Fatalf("status=%d", sink.records[0].Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResponseCapturePreservesConnectionHijacking(t *testing.T) {
|
||||||
|
serverConnection, clientConnection := net.Pipe()
|
||||||
|
defer serverConnection.Close()
|
||||||
|
defer clientConnection.Close()
|
||||||
|
underlying := &hijackableRecorder{
|
||||||
|
ResponseRecorder: httptest.NewRecorder(),
|
||||||
|
connection: serverConnection,
|
||||||
|
buffer: bufio.NewReadWriter(bufio.NewReader(serverConnection), bufio.NewWriter(serverConnection)),
|
||||||
|
}
|
||||||
|
capture := &responseCapture{ResponseWriter: underlying, status: http.StatusOK}
|
||||||
|
hijacker, ok := any(capture).(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("request evidence wrapper does not expose http.Hijacker")
|
||||||
|
}
|
||||||
|
connection, buffer, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if connection != serverConnection || buffer != underlying.buffer {
|
||||||
|
t.Fatal("hijacked connection was not passed through")
|
||||||
|
}
|
||||||
|
if capture.status != http.StatusSwitchingProtocols || !capture.wroteHeader || capture.bytes != 0 {
|
||||||
|
t.Fatalf("capture after hijack=%+v", capture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user