Add further information and add proper styling and layour for a consistent view across devices and browsers

This commit is contained in:
2026-07-26 19:37:38 +01:00
parent a06d35e3db
commit e4b0a8ca6d
2 changed files with 272 additions and 41 deletions

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"embed"
"fmt" "fmt"
"html/template" "html/template"
"io" "io"
@@ -8,57 +9,44 @@ import (
"net/http" "net/http"
) )
//go:embed templates/*.html
var templates embed.FS
var echoTemplate = template.Must(
template.ParseFS(templates, "templates/echo.html"),
)
type EchoData struct { type EchoData struct {
Method string Method string
URL string URL string
Headers map[string][]string Path string
Body string Query string
Host string
Proto string
RemoteAddr string
Headers map[string][]string
Body string
TLS bool
} }
var echoTemplate = template.Must(template.New("echo").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Echo Server</title>
<style>
body { font-family: monospace; }
.header { font-weight: bold; margin-top: 1em; }
pre {margin: 4px; }
</style>
</head>
<body>
<h2>{{.Method}} {{.URL}}</h2>
<h3>Headers:</h3>
{{- range $name, $values := .Headers}}
<div class="header">{{$name}}</div>
<pre>{{- range $_, $v := $values}} {{$v}}
{{- end}}</pre>
{{- end}}
<h3>Body:</h3>
<pre>
{{.Body}}
</pre>
</body>
</html>
`))
func echoHandler(w http.ResponseWriter, r *http.Request) { func echoHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
bodyBytes, err := io.ReadAll(r.Body) bodyBytes, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
http.Error(w, "Failed to read body: "+err.Error(), http.StatusBadRequest) http.Error(w, "Failed to read body: "+err.Error(), http.StatusBadRequest)
return return
} }
r.Body.Close()
data := EchoData{ data := EchoData{
Method: r.Method, Method: r.Method,
URL: r.URL.String(), URL: r.URL.String(),
Headers: r.Header, Path: r.URL.Path,
Body: string(bodyBytes), Query: r.URL.RawQuery,
Host: r.Host,
RemoteAddr: r.RemoteAddr,
Proto: r.Proto,
Headers: r.Header,
Body: string(bodyBytes),
TLS: r.TLS != nil,
} }
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")

243
src/templates/echo.html Normal file
View File

@@ -0,0 +1,243 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Echo Server</title>
<style>
:root {
--bg: #f3f4f6;
--card: #ffffff;
--border: #d1d5db;
--heading: #111827;
--text: #374151;
--muted: #6b7280;
--code: #f9fafb;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 1rem;
background: var(--bg);
color: var(--text);
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 15px;
line-height: 1.5;
}
.container {
max-width: 1100px;
margin: auto;
}
h1 {
color: var(--heading);
font-size: 1.6rem;
margin-bottom: .25rem;
}
.subtitle {
color: var(--muted);
margin-bottom: 1.5rem;
font-family: monospace;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 1rem;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.card h2 {
margin-top: 0;
font-size: 1.1rem;
color: var(--heading);
border-bottom: 1px solid var(--border);
padding-bottom: .5rem;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: .4rem .75rem;
border-bottom: 1px solid var(--border);
text-align: left;
vertical-align: top;
}
td {
word-break: break-all;
overflow-wrap: anywhere;
}
th {
padding-left: 0;
padding-right: 1.5rem;
width: 35%;
color: var(--muted);
font-weight: 600;
}
tr:last-child th,
tr:last-child td {
border-bottom: none;
}
pre {
background: var(--code);
border: 1px solid var(--border);
border-radius: 6px;
padding: .75rem;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
font-family: monospace;
}
.header-name {
font-family: monospace;
color: var(--heading);
font-weight: bold;
}
@media (max-width: 600px) {
body {
padding: .5rem;
}
th {
width: 45%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Echo Server</h1>
<div class="subtitle">{{.Method}} {{.URL}}</div>
<div class="grid">
<div class="card">
<h2>Last Hop Connection</h2>
<table>
<tr>
<th>Remote Address</th>
<td>{{.RemoteAddr}}</td>
</tr>
<tr>
<th>TLS</th>
<td>{{.TLS}}</td>
</tr>
<tr>
<th>Protocol</th>
<td>{{.Proto}}</td>
</tr>
</table>
</div>
<div class="card">
<h2>Request</h2>
<table>
<tr>
<th>Method</th>
<td>{{.Method}}</td>
</tr>
<tr>
<th>Host</th>
<td>{{.Host}}</td>
</tr>
<tr>
<th>Path</th>
<td>{{.Path}}</td>
</tr>
<tr>
<th>Query</th>
<td>{{.Query}}</td>
</tr>
<tr>
<th>URL</th>
<td>{{.URL}}</td>
</tr>
</table>
</div>
</div>
<div class="card">
<h2>Forwarding Headers</h2>
<table>
<tr>
<th>X-Forwarded-For</th>
<td>{{index .Headers "X-Forwarded-For"}}</td>
</tr>
<tr>
<th>X-Real-IP</th>
<td>{{index .Headers "X-Real-IP"}}</td>
</tr>
<tr>
<th>X-Forwarded-Proto</th>
<td>{{index .Headers "X-Forwarded-Proto"}}</td>
</tr>
<tr>
<th>X-Forwarded-Host</th>
<td>{{index .Headers "X-Forwarded-Host"}}</td>
</tr>
<tr>
<th>Forwarded</th>
<td>{{index .Headers "Forwarded"}}</td>
</tr>
</table>
</div>
<div class="card">
<h2>Headers</h2>
<table>
{{range $name, $values := .Headers}}
<tr>
<th class="header-name">{{$name}}</th>
<td>
{{range $values}}
<div>{{.}}</div>
{{end}}
</td>
</tr>
{{end}}
</table>
</div>
<div class="card">
<h2>Body</h2>
<pre>{{.Body}}</pre>
</div>
</div>
</body>
</html>