Skip to content

Commit a9ca66e

Browse files
committed
request/response body download
1 parent 07b6da1 commit a9ca66e

File tree

4 files changed

+335
-162
lines changed

4 files changed

+335
-162
lines changed

dashboard/handler.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ func NewHandler(options HandlerOptions) *Handler {
4848
mux.HandleFunc("/event/{eventId}", handler.getEventDetails)
4949
mux.HandleFunc("/events-sse", handler.getEventsSSE)
5050

51+
// Download handlers
52+
mux.HandleFunc("/download/request-body/{eventId}", handler.downloadRequestBody)
53+
mux.HandleFunc("/download/response-body/{eventId}", handler.downloadResponseBody)
54+
5155
// Test routes
5256
mux.HandleFunc("/logs", handler.getLogs)
5357
mux.HandleFunc("/http-client-requests", handler.getHTTPClientRequests)
@@ -290,3 +294,99 @@ func truncate(s string, maxLen int) string {
290294
}
291295
return s[:maxLen] + "..."
292296
}
297+
298+
// downloadRequestBody handles downloading the request body for an event
299+
func (h *Handler) downloadRequestBody(w http.ResponseWriter, r *http.Request) {
300+
idStr := r.PathValue("eventId")
301+
eventID, err := uuid.FromString(idStr)
302+
if err != nil {
303+
http.Error(w, "Invalid event id", http.StatusBadRequest)
304+
return
305+
}
306+
307+
event, exists := h.eventCollector.GetEvent(eventID)
308+
if !exists {
309+
http.Error(w, "Event not found", http.StatusNotFound)
310+
return
311+
}
312+
313+
var body []byte
314+
var contentType string
315+
316+
switch data := event.Data.(type) {
317+
case collector.HTTPRequest:
318+
if data.RequestBody == nil {
319+
http.Error(w, "No request body available", http.StatusNotFound)
320+
return
321+
}
322+
body = data.RequestBody.Bytes()
323+
contentType = data.RequestHeaders.Get("Content-Type")
324+
case collector.HTTPServerRequest:
325+
if data.RequestBody == nil {
326+
http.Error(w, "No request body available", http.StatusNotFound)
327+
return
328+
}
329+
body = data.RequestBody.Bytes()
330+
contentType = data.RequestHeaders.Get("Content-Type")
331+
default:
332+
http.Error(w, "Event type does not have a request body", http.StatusBadRequest)
333+
return
334+
}
335+
336+
if contentType == "" {
337+
contentType = "application/octet-stream"
338+
}
339+
340+
w.Header().Set("Content-Type", contentType)
341+
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=request-body-%s", eventID))
342+
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
343+
w.Write(body)
344+
}
345+
346+
// downloadResponseBody handles downloading the response body for an event
347+
func (h *Handler) downloadResponseBody(w http.ResponseWriter, r *http.Request) {
348+
idStr := r.PathValue("eventId")
349+
eventID, err := uuid.FromString(idStr)
350+
if err != nil {
351+
http.Error(w, "Invalid event id", http.StatusBadRequest)
352+
return
353+
}
354+
355+
event, exists := h.eventCollector.GetEvent(eventID)
356+
if !exists {
357+
http.Error(w, "Event not found", http.StatusNotFound)
358+
return
359+
}
360+
361+
var body []byte
362+
var contentType string
363+
364+
switch data := event.Data.(type) {
365+
case collector.HTTPRequest:
366+
if data.ResponseBody == nil {
367+
http.Error(w, "No response body available", http.StatusNotFound)
368+
return
369+
}
370+
body = data.ResponseBody.Bytes()
371+
contentType = data.ResponseHeaders.Get("Content-Type")
372+
case collector.HTTPServerRequest:
373+
if data.ResponseBody == nil {
374+
http.Error(w, "No response body available", http.StatusNotFound)
375+
return
376+
}
377+
body = data.ResponseBody.Bytes()
378+
contentType = data.ResponseHeaders.Get("Content-Type")
379+
default:
380+
http.Error(w, "Event type does not have a response body", http.StatusBadRequest)
381+
return
382+
}
383+
384+
if contentType == "" {
385+
contentType = "application/octet-stream"
386+
}
387+
388+
w.Header().Set("Content-Type", contentType)
389+
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=response-body-%s", eventID))
390+
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
391+
w.Write(body)
392+
}

dashboard/views/event-details.templ

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,17 @@ templ HTTPRequestDetails(event *collector.Event, request collector.HTTPRequest)
158158
<!-- Request Body -->
159159
if request.RequestBody != nil && request.RequestBody.Size() > 0 {
160160
<div class="mb-4">
161-
<h4 class="text-sm font-semibold mb-2">Body</h4>
161+
<div class="flex items-center justify-between mb-2">
162+
<h4 class="text-sm font-semibold">Body</h4>
163+
<a
164+
href={ templ.SafeURL(fmt.Sprintf("/_devlog/download/request-body/%s", event.ID)) }
165+
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
166+
download
167+
>
168+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
169+
Download
170+
</a>
171+
</div>
162172
@BodyContent(request.RequestBody.String(), request.RequestHeaders.Get("Content-Type"), 10000)
163173
</div>
164174
}
@@ -196,7 +206,17 @@ templ HTTPRequestDetails(event *collector.Event, request collector.HTTPRequest)
196206
<!-- Response Body -->
197207
if request.ResponseBody != nil && request.ResponseBody.Size() > 0 {
198208
<div class="mb-4">
199-
<h4 class="text-sm font-semibold mb-2">Body</h4>
209+
<div class="flex items-center justify-between mb-2">
210+
<h4 class="text-sm font-semibold">Body</h4>
211+
<a
212+
href={ templ.SafeURL(fmt.Sprintf("/_devlog/download/response-body/%s", event.ID)) }
213+
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
214+
download
215+
>
216+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
217+
Download
218+
</a>
219+
</div>
200220
@BodyContent(request.ResponseBody.String(), request.ResponseHeaders.Get("Content-Type"), 10000)
201221
</div>
202222
}
@@ -285,7 +305,17 @@ templ HTTPServerRequestDetails(event *collector.Event, request collector.HTTPSer
285305
<!-- Request Body -->
286306
if request.RequestBody != nil && request.RequestBody.Size() > 0 {
287307
<div class="mb-4">
288-
<h4 class="text-sm font-semibold mb-2">Body</h4>
308+
<div class="flex items-center justify-between mb-2">
309+
<h4 class="text-sm font-semibold">Body</h4>
310+
<a
311+
href={ templ.SafeURL(fmt.Sprintf("/_devlog/download/request-body/%s", event.ID)) }
312+
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
313+
download
314+
>
315+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
316+
Download
317+
</a>
318+
</div>
289319
@BodyContent(request.RequestBody.String(), request.RequestHeaders.Get("Content-Type"), 10000)
290320
</div>
291321
}
@@ -323,7 +353,17 @@ templ HTTPServerRequestDetails(event *collector.Event, request collector.HTTPSer
323353
<!-- Response Body -->
324354
if request.ResponseBody != nil && request.ResponseBody.Size() > 0 {
325355
<div class="mb-4">
326-
<h4 class="text-sm font-semibold mb-2">Body</h4>
356+
<div class="flex items-center justify-between mb-2">
357+
<h4 class="text-sm font-semibold">Body</h4>
358+
<a
359+
href={ templ.SafeURL(fmt.Sprintf("/_devlog/download/response-body/%s", event.ID)) }
360+
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
361+
download
362+
>
363+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>
364+
Download
365+
</a>
366+
</div>
327367
@BodyContent(request.ResponseBody.String(), request.ResponseHeaders.Get("Content-Type"), 10000)
328368
</div>
329369
}

0 commit comments

Comments
 (0)