Documentation for controlling and preserving Bose SoundTouch devices
The current request recording system has fundamental issues when dealing with request cloning, body consumption, and multiple response scenarios. Specifically:
Local Recording (complete):
### POST /v1/scmudc/A81B6A536A98
POST /v1/scmudc/A81B6A536A98
Host: events.api.bosecm.com
Content-Type: text/json; charset=utf-8
Content-Length: 587
Authorization: Bearer jGwEmFWr...
{"envelope":{"monoTime":234906,"payloadProtocolVersion":"3.1","payloadType":"scmudc","protocolVersion":"1.0","time":"2026-02-25T23:03:14.976349+00:00","uniqueId":"A81B6A536A98"},"payload":{"deviceInfo":{"boseID":"3230304","deviceID":"A81B6A536A98","deviceType":"SoundTouch 10","serialNumber":"I6332527703739342000020","softwareVersion":"27.0.6.46330.5043500 epdbuild.trunk.hepdswbld04.2022-08-04T11:20:29","systemSerialNumber":"069231P63364828AE"},"events":[{"data":{"play-state":"PAUSE_STATE"},"monoTime":234904,"time":"2026-02-25T23:03:14.973466+00:00","type":"play-state-changed"}]}}
> {%
// Response: 200 OK
%}
Mirror Recording (missing body):
### POST /v1/scmudc/A81B6A536A98
POST /v1/scmudc/A81B6A536A98
Host: events.api.bosecm.com
Content-Type: text/json; charset=utf-8
Content-Length: 587
Authorization: Bearer jGwEmFWr...
> {%
// Response: 200 OK
// Headers:
// X-Proxy-Origin: upstream-mirror
%}
Current middleware execution order:
1. MirrorMiddleware - Buffers body, creates clones
2. RecordMiddleware - Also buffers body
3. Application Handler - Processes request
4. Mirror Execution - Async/sync mirror to upstream
5. Recording - Multiple recording points
Problems:
Create immutable request snapshots early in the request lifecycle and propagate them through the Request Context. This ensures all downstream consumers (Mirroring, Recording, Parity Check) use identical data without re-reading the request body.
┌─────────────────┐
│ Original Request│
└─────────┬───────┘
│
▼
┌─────────────────┐ ┌──────────────────┐
│ Snapshot Creator│───▶│ Request Context │
│ (Middleware) │ │ (Pointer-based) │
└─────────┬───────┘ └──────────────────┘
│ │
▼ │ (Safe for async)
┌─────────────────┐ │
│ Middleware │◀─────────────┘
│ Chain │
└─────────┬───────┘
│
┌───▼────┐ ┌─────────┐ ┌──────────────┐
│ Local │ │ Mirror │ │ Recording │
│Handler │ │Execution│ │ System │
└────────┘ └─────────┘ └──────────────┘
type RequestSnapshot struct {
Method string
URL *url.URL
Headers http.Header
Body []byte
Host string
Timestamp time.Time
}
// Typed key for context safety
type contextKey struct{ name string }
var SnapshotKey = &contextKey{"request_snapshot"}
func (s *Server) SnapshotMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. Capture body once with size limit (e.g. 2MB)
body, _ := io.ReadAll(io.LimitReader(r.Body, 2*1024*1024))
r.Body.Close()
// 2. Create snapshot
snapshot := &RequestSnapshot{
Method: r.Method,
URL: cloneURL(r.URL),
Headers: r.Header.Clone(),
Body: body,
Host: r.Host,
Timestamp: time.Now(),
}
// 3. Inject pointer into context
ctx := context.WithValue(r.Context(), SnapshotKey, snapshot)
// 4. Restore r.Body for downstream compatibility
r = r.WithContext(ctx)
r.Body = io.NopCloser(bytes.NewReader(snapshot.Body))
next.ServeHTTP(w, r)
})
}
Consumers (Mirror/Record) retrieve the snapshot directly from context:
snapshot, ok := r.Context().Value(SnapshotKey).(*RequestSnapshot)
if ok {
// Use snapshot.Body directly instead of io.ReadAll(r.Body)
}
To protect MicroSD health and optimize for limited memory:
.http recording is generated.sync.Pool for temporary buffers to reduce GC churn on the single-core/low-memory SoC.type ResponseRecorder struct {
http.ResponseWriter
snapshot *ResponseSnapshot
snapshotID string
source string
startTime time.Time
}
func (r *ResponseRecorder) WriteHeader(statusCode int) {
r.snapshot.StatusCode = statusCode
r.snapshot.Headers = r.Header().Clone()
r.ResponseWriter.WriteHeader(statusCode)
}
func (r *ResponseRecorder) Write(data []byte) (int, error) {
r.snapshot.Body = append(r.snapshot.Body, data...)
return r.ResponseWriter.Write(data)
}
func (r *ResponseRecorder) finalize() {
r.snapshot.Duration = time.Since(r.startTime)
r.snapshot.Timestamp = time.Now()
}
type RecordingManager struct {
storage SnapshotStorage
recorder *Recorder
patterns []string
}
func (rm *RecordingManager) RecordInteraction(snapshotID string, response *ResponseSnapshot) {
// Retrieve immutable request snapshot
request, exists := rm.storage.Get(snapshotID)
if !exists {
log.Printf("Request snapshot not found: %s", snapshotID)
return
}
// Record with guaranteed data integrity
rm.recorder.RecordInteraction(request, response)
}
func (r *Recorder) RecordInteraction(req *RequestSnapshot, res *ResponseSnapshot) error {
// Generate .http file with complete data
var buf bytes.Buffer
// Write request
fmt.Fprintf(&buf, "### %s %s\n", req.Method, req.URL.String())
fmt.Fprintf(&buf, "%s %s\n", req.Method, req.URL.String())
fmt.Fprintf(&buf, "Host: %s\n", req.Host)
for k, vv := range req.Headers {
for _, v := range vv {
fmt.Fprintf(&buf, "%s: %s\n", k, v)
}
}
buf.WriteString("\n")
buf.Write(req.Body)
buf.WriteString("\n\n")
// Write response
buf.WriteString("> {% \n")
fmt.Fprintf(&buf, " // Response: %d %s\n", res.StatusCode, http.StatusText(res.StatusCode))
buf.WriteString(" // Headers:\n")
for k, vv := range res.Headers {
for _, v := range vv {
fmt.Fprintf(&buf, " // %s: %s\n", k, v)
}
}
buf.WriteString("%}\n\n")
if len(res.Body) > 0 {
buf.WriteString("/*\n")
buf.Write(res.Body)
buf.WriteString("\n*/\n")
} else {
buf.WriteString("// [Binary response body: 0 bytes]\n")
}
// Write to file
return r.writeToFile(buf.Bytes(), req, res)
}
sync.Pool for byte buffersThis snapshot-based approach provides a robust foundation for reliable request recording while solving the current issues with body consumption and data inconsistency. The phased implementation ensures minimal disruption while delivering immediate benefits.