From 4ecd01bee59e220ac2908244bd0e291f9bf0f91a Mon Sep 17 00:00:00 2001
From: maoren <747293336.com>
Date: Fri, 20 Mar 2026 11:42:27 +0000
Subject: [PATCH] feat: add client remote address display support
- Add SEND_SERVER_REMOTE env var and X-Send-Server-Remote heade
r
- Add RemoteAddr, RemoteIP, RemotePort to echo response
- Make WebSocket UI responsive for mobile devices
---
README.md | 34 +++++++++++++
cmd/echo-server/html/frontend.tmpl.html | 66 +++++++++++++++++++++++++
cmd/echo-server/main.go | 45 +++++++++++++----
3 files changed, 136 insertions(+), 9 deletions(-)
diff --git a/README.md b/README.md
index 798b509..3c2c414 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,23 @@ information about HTTP request headers and bodies back to the client.
The `PORT` environment variable sets the server port, which defaults to `8080`.
+### SEND_SERVER_REMOTE
+
+The `SEND_SERVER_REMOTE` variable set remote address display or
+
+```bash
+curl -H '' -H 'X-Send-Server-Remote: true' http://dnsname.domain
+```
+
+Add RemoteAddr, RemoteIP, RemotePort to echo response
+
+```
+RemoteAddr: xx:yy
+RemoteIP: xx
+RemotePort: yy
+```
+
+
### Logging
Set the `LOG_HTTP_HEADERS` environment variable to print request headers to
@@ -62,6 +79,23 @@ go get -u github.com/jmalloc/echo-server/...
PORT=10000 echo-server
```
+```
+export GOPROXY=https://goproxy.cn,direct && SEND_SERVER_REMOTE=false PORT=10001 go run cmd/echo-server/main.go
+```
+
+## build and package
+
+```bash
+go build -o artifacts/echo-server ./cmd/echo-server
+# build
+GOOS=linux GOARCH=amd64 go build -o artifacts/echo-server_linux_amd64 ./cmd/echo-server
+GOOS=linux GOARCH=arm64 go build -o artifacts/echo-server_linux_arm64 ./cmd/echo-server
+GOOS=darwin GOARCH=amd64 go build -o artifacts/echo-server_mac_x86_64 ./cmd/echo-server
+GOOS=darwin GOARCH=arm64 go build -o artifacts/echo-server_mac_arm64 ./cmd/echo-server
+GOOS=windows GOARCH=amd64 go build -o artifacts/echo-server_windows_amd64 ./cmd/echo-server
+ls -lah artifacts/*
+```
+
### Running under Docker
To run the latest version as a container:
diff --git a/cmd/echo-server/html/frontend.tmpl.html b/cmd/echo-server/html/frontend.tmpl.html
index 657f239..5e108fe 100644
--- a/cmd/echo-server/html/frontend.tmpl.html
+++ b/cmd/echo-server/html/frontend.tmpl.html
@@ -1,6 +1,7 @@
websocket
+
diff --git a/cmd/echo-server/main.go b/cmd/echo-server/main.go
index 16596e8..a800a4b 100644
--- a/cmd/echo-server/main.go
+++ b/cmd/echo-server/main.go
@@ -89,6 +89,16 @@ func handler(wr http.ResponseWriter, req *http.Request) {
"false",
)
+ sendServerRemoteString := os.Getenv("SEND_SERVER_REMOTE")
+ if v := req.Header.Get("X-Send-Server-Remote"); v != "" {
+ sendServerRemoteString = v
+ }
+
+ sendServerRemote := strings.EqualFold(
+ sendServerRemoteString,
+ "true",
+ )
+
for _, line := range os.Environ() {
parts := strings.SplitN(line, "=", 2)
key, value := parts[0], parts[1]
@@ -102,17 +112,17 @@ func handler(wr http.ResponseWriter, req *http.Request) {
}
if websocket.IsWebSocketUpgrade(req) {
- serveWebSocket(wr, req, sendServerHostname)
+ serveWebSocket(wr, req, sendServerHostname, sendServerRemote)
} else if path.Base(req.URL.Path) == ".ws" {
serveFrontend(wr, req)
} else if path.Base(req.URL.Path) == ".sse" {
- serveSSE(wr, req, sendServerHostname)
+ serveSSE(wr, req, sendServerHostname, sendServerRemote)
} else {
- serveHTTP(wr, req, sendServerHostname)
+ serveHTTP(wr, req, sendServerHostname, sendServerRemote)
}
}
-func serveWebSocket(wr http.ResponseWriter, req *http.Request, sendServerHostname bool) {
+func serveWebSocket(wr http.ResponseWriter, req *http.Request, sendServerHostname bool, sendServerRemote bool) {
connection, err := upgrader.Upgrade(wr, req, nil)
if err != nil {
fmt.Printf("%s | %s\n", req.RemoteAddr, err)
@@ -188,7 +198,7 @@ func serveFrontend(wr http.ResponseWriter, req *http.Request) {
wr.WriteHeader(200)
}
-func serveHTTP(wr http.ResponseWriter, req *http.Request, sendServerHostname bool) {
+func serveHTTP(wr http.ResponseWriter, req *http.Request, sendServerHostname bool, sendServerRemote bool) {
wr.Header().Add("Content-Type", "text/plain")
wr.WriteHeader(200)
@@ -201,17 +211,17 @@ func serveHTTP(wr http.ResponseWriter, req *http.Request, sendServerHostname boo
}
}
- writeRequest(wr, req)
+ writeRequest(wr, req, sendServerRemote)
}
-func serveSSE(wr http.ResponseWriter, req *http.Request, sendServerHostname bool) {
+func serveSSE(wr http.ResponseWriter, req *http.Request, sendServerHostname bool, sendServerRemote bool) {
if _, ok := wr.(http.Flusher); !ok {
http.Error(wr, "Streaming unsupported!", http.StatusInternalServerError)
return
}
var echo strings.Builder
- writeRequest(&echo, req)
+ writeRequest(&echo, req, sendServerRemote)
wr.Header().Set("Content-Type", "text/event-stream")
wr.Header().Set("Cache-Control", "no-cache")
@@ -290,11 +300,28 @@ func writeSSEField(
}
// writeRequest writes request headers to w.
-func writeRequest(w io.Writer, req *http.Request) {
+func writeRequest(w io.Writer, req *http.Request, sendServerRemote bool) {
fmt.Fprintf(w, "%s %s %s\n", req.Method, req.URL, req.Proto)
fmt.Fprintln(w, "")
fmt.Fprintf(w, "Host: %s\n", req.Host)
+
+ if sendServerRemote {
+ fmt.Fprintf(w, "RemoteAddr: %s\n", req.RemoteAddr)
+
+ // 解析IP地址和端口
+ ip := req.RemoteAddr
+ port := ""
+ if idx := strings.LastIndex(req.RemoteAddr, ":"); idx != -1 {
+ ip = req.RemoteAddr[:idx]
+ port = req.RemoteAddr[idx+1:]
+ // 移除IPv6地址的方括号
+ ip = strings.TrimPrefix(strings.TrimSuffix(ip, "]"), "[")
+ }
+ fmt.Fprintf(w, "RemoteIP: %s\n", ip)
+ fmt.Fprintf(w, "RemotePort: %s\n", port)
+ }
+
printHeaders(w, req.Header)
var body bytes.Buffer