|
8 | 8 | "io/ioutil" |
9 | 9 | "net/http" |
10 | 10 | "os" |
| 11 | + "strconv" |
| 12 | + "strings" |
| 13 | + "time" |
11 | 14 |
|
12 | 15 | "github.com/gorilla/websocket" |
13 | 16 | "golang.org/x/net/http2" |
@@ -41,6 +44,7 @@ var upgrader = websocket.Upgrader{ |
41 | 44 | } |
42 | 45 |
|
43 | 46 | func handler(wr http.ResponseWriter, req *http.Request) { |
| 47 | + defer req.Body.Close() |
44 | 48 |
|
45 | 49 | if os.Getenv("LOG_HTTP_BODY") != "" || os.Getenv("LOG_HTTP_HEADERS") != "" { |
46 | 50 | fmt.Printf("-------- %s | %s %s\n", req.RemoteAddr, req.Method, req.URL) |
@@ -80,6 +84,8 @@ func handler(wr http.ResponseWriter, req *http.Request) { |
80 | 84 | wr.Header().Add("Content-Type", "text/html") |
81 | 85 | wr.WriteHeader(200) |
82 | 86 | io.WriteString(wr, websocketHTML) // nolint:errcheck |
| 87 | + } else if req.URL.Path == "/.sse" { |
| 88 | + serveSSE(wr, req) |
83 | 89 | } else { |
84 | 90 | serveHTTP(wr, req) |
85 | 91 | } |
@@ -143,15 +149,109 @@ func serveHTTP(wr http.ResponseWriter, req *http.Request) { |
143 | 149 | fmt.Fprintf(wr, "Server hostname unknown: %s\n\n", err.Error()) |
144 | 150 | } |
145 | 151 |
|
146 | | - fmt.Fprintf(wr, "%s %s %s\n", req.Proto, req.Method, req.URL) |
147 | | - fmt.Fprintln(wr, "") |
148 | | - fmt.Fprintf(wr, "Host: %s\n", req.Host) |
| 152 | + writeRequest(wr, req) |
| 153 | +} |
| 154 | + |
| 155 | +func serveSSE(wr http.ResponseWriter, req *http.Request) { |
| 156 | + if _, ok := wr.(http.Flusher); !ok { |
| 157 | + http.Error(wr, "Streaming unsupported!", http.StatusInternalServerError) |
| 158 | + return |
| 159 | + } |
| 160 | + |
| 161 | + var echo strings.Builder |
| 162 | + writeRequest(&echo, req) |
| 163 | + |
| 164 | + wr.Header().Set("Content-Type", "text/event-stream") |
| 165 | + wr.Header().Set("Cache-Control", "no-cache") |
| 166 | + wr.Header().Set("Connection", "keep-alive") |
| 167 | + wr.Header().Set("Access-Control-Allow-Origin", "*") |
| 168 | + |
| 169 | + var id int |
| 170 | + |
| 171 | + // Write an event about the server that is serving this request. |
| 172 | + if host, err := os.Hostname(); err == nil { |
| 173 | + writeSSE( |
| 174 | + wr, |
| 175 | + req, |
| 176 | + &id, |
| 177 | + "server", |
| 178 | + host, |
| 179 | + ) |
| 180 | + } |
| 181 | + |
| 182 | + // Write an event that echoes back the request. |
| 183 | + writeSSE( |
| 184 | + wr, |
| 185 | + req, |
| 186 | + &id, |
| 187 | + "request", |
| 188 | + echo.String(), |
| 189 | + ) |
| 190 | + |
| 191 | + // Then send a counter event every second. |
| 192 | + ticker := time.NewTicker(1 * time.Second) |
| 193 | + defer ticker.Stop() |
| 194 | + |
| 195 | + for { |
| 196 | + select { |
| 197 | + case <-req.Context().Done(): |
| 198 | + return |
| 199 | + case t := <-ticker.C: |
| 200 | + writeSSE( |
| 201 | + wr, |
| 202 | + req, |
| 203 | + &id, |
| 204 | + "time", |
| 205 | + t.Format(time.RFC3339), |
| 206 | + ) |
| 207 | + } |
| 208 | + } |
| 209 | +} |
| 210 | + |
| 211 | +// writeSSE sends a server-sent event and logs it to the console. |
| 212 | +func writeSSE( |
| 213 | + wr http.ResponseWriter, |
| 214 | + req *http.Request, |
| 215 | + id *int, |
| 216 | + event, data string, |
| 217 | +) { |
| 218 | + *id++ |
| 219 | + writeSSEField(wr, req, "event", event) |
| 220 | + writeSSEField(wr, req, "data", data) |
| 221 | + writeSSEField(wr, req, "id", strconv.Itoa(*id)) |
| 222 | + fmt.Fprintf(wr, "\n") |
| 223 | + wr.(http.Flusher).Flush() |
| 224 | +} |
| 225 | + |
| 226 | +// writeSSEField sends a single field within an event. |
| 227 | +func writeSSEField( |
| 228 | + wr http.ResponseWriter, |
| 229 | + req *http.Request, |
| 230 | + k, v string, |
| 231 | +) { |
| 232 | + for _, line := range strings.Split(v, "\n") { |
| 233 | + fmt.Fprintf(wr, "%s: %s\n", k, line) |
| 234 | + fmt.Printf("%s | sse | %s: %s\n", req.RemoteAddr, k, line) |
| 235 | + } |
| 236 | +} |
| 237 | + |
| 238 | +// writeRequest writes request headers to w. |
| 239 | +func writeRequest(w io.Writer, req *http.Request) { |
| 240 | + fmt.Fprintf(w, "%s %s %s\n", req.Proto, req.Method, req.URL) |
| 241 | + fmt.Fprintln(w, "") |
| 242 | + |
| 243 | + fmt.Fprintf(w, "Host: %s\n", req.Host) |
149 | 244 | for key, values := range req.Header { |
150 | 245 | for _, value := range values { |
151 | | - fmt.Fprintf(wr, "%s: %s\n", key, value) |
| 246 | + fmt.Fprintf(w, "%s: %s\n", key, value) |
152 | 247 | } |
153 | 248 | } |
154 | 249 |
|
155 | | - fmt.Fprintln(wr, "") |
156 | | - io.Copy(wr, req.Body) // nolint:errcheck |
| 250 | + var body bytes.Buffer |
| 251 | + io.Copy(&body, req.Body) // nolint:errcheck |
| 252 | + |
| 253 | + if body.Len() > 0 { |
| 254 | + fmt.Fprintln(w, "") |
| 255 | + body.WriteTo(w) // nolint:errcheck |
| 256 | + } |
157 | 257 | } |
0 commit comments