Add further information and add proper styling and layour for a consistent view across devices and browsers
This commit is contained in:
70
src/main.go
70
src/main.go
@@ -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
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