Add further information and add proper styling and layour for a consistent view across devices and browsers
This commit is contained in:
54
src/main.go
54
src/main.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
@@ -8,57 +9,44 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed templates/*.html
|
||||
var templates embed.FS
|
||||
var echoTemplate = template.Must(
|
||||
template.ParseFS(templates, "templates/echo.html"),
|
||||
)
|
||||
|
||||
type EchoData struct {
|
||||
Method string
|
||||
URL string
|
||||
Path 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) {
|
||||
defer r.Body.Close()
|
||||
bodyBytes, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read body: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
r.Body.Close()
|
||||
|
||||
data := EchoData{
|
||||
Method: r.Method,
|
||||
URL: r.URL.String(),
|
||||
Path: r.URL.Path,
|
||||
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")
|
||||
|
||||
243
src/templates/echo.html
Normal file
243
src/templates/echo.html
Normal 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>
|
||||
Reference in New Issue
Block a user