upstream_go.go (6033B)
1 /* 2 This file is part of Paivana. 3 Copyright (C) 2026 Taler Systems SA 4 5 Paivana is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Affero General Public License 7 as published by the Free Software Foundation; either version 8 3, or (at your option) any later version. 9 10 Paivana is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public 16 License along with Paivana; see the file COPYING. If not, 17 write to the Free Software Foundation, Inc., 51 Franklin 18 Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21 // upstream_go: Go-based upstream HTTP server used by the 22 // paivana reverse-proxy tests. Implements the same small set 23 // of canned endpoints as upstream_mhd.c. 24 25 package main 26 27 import ( 28 "fmt" 29 "io" 30 "net/http" 31 "os" 32 "sort" 33 "strconv" 34 "strings" 35 "time" 36 ) 37 38 const upstreamName = "go" 39 40 func setHeaders(w http.ResponseWriter) { 41 w.Header().Set("X-Upstream", upstreamName) 42 } 43 44 func hello(w http.ResponseWriter, r *http.Request) { 45 setHeaders(w) 46 w.Header().Set("Content-Type", "text/plain") 47 if r.Method == http.MethodHead { 48 w.WriteHeader(200) 49 return 50 } 51 fmt.Fprintf(w, "Hello from %s\n", upstreamName) 52 } 53 54 func status(w http.ResponseWriter, r *http.Request) { 55 setHeaders(w) 56 path := strings.TrimPrefix(r.URL.Path, "/status/") 57 code, err := strconv.Atoi(path) 58 if err != nil || code < 100 || code > 599 { 59 code = 500 60 } 61 w.Header().Set("Content-Type", "text/plain") 62 w.WriteHeader(code) 63 fmt.Fprintf(w, "status %d\n", code) 64 } 65 66 func large(w http.ResponseWriter, r *http.Request) { 67 setHeaders(w) 68 path := strings.TrimPrefix(r.URL.Path, "/large/") 69 n, err := strconv.Atoi(path) 70 if err != nil || n < 0 { 71 n = 0 72 } 73 if n > 10*1024*1024 { 74 n = 10 * 1024 * 1024 75 } 76 buf := make([]byte, n) 77 for i := 0; i < n; i++ { 78 buf[i] = byte('A' + i%26) 79 } 80 w.Header().Set("Content-Type", "application/octet-stream") 81 w.WriteHeader(200) 82 w.Write(buf) 83 } 84 85 func slow(w http.ResponseWriter, r *http.Request) { 86 setHeaders(w) 87 path := strings.TrimPrefix(r.URL.Path, "/slow/") 88 ms, err := strconv.Atoi(path) 89 if err != nil || ms < 0 { 90 ms = 0 91 } 92 if ms > 30000 { 93 ms = 30000 94 } 95 time.Sleep(time.Duration(ms) * time.Millisecond) 96 w.Header().Set("Content-Type", "text/plain") 97 w.WriteHeader(200) 98 fmt.Fprint(w, "slept\n") 99 } 100 101 func echo(w http.ResponseWriter, r *http.Request) { 102 setHeaders(w) 103 w.Header().Set("Content-Type", "application/octet-stream") 104 body, _ := io.ReadAll(r.Body) 105 w.WriteHeader(200) 106 w.Write(body) 107 } 108 109 func upload(w http.ResponseWriter, r *http.Request, label string) { 110 setHeaders(w) 111 body, _ := io.ReadAll(r.Body) 112 w.Header().Set("Content-Type", "text/plain") 113 w.WriteHeader(200) 114 fmt.Fprintf(w, "%s %d bytes\n", label, len(body)) 115 } 116 117 func echoHeaders(w http.ResponseWriter, r *http.Request) { 118 setHeaders(w) 119 var keys []string 120 for k := range r.Header { 121 keys = append(keys, k) 122 } 123 sort.Strings(keys) 124 w.Header().Set("Content-Type", "text/plain") 125 w.WriteHeader(200) 126 for _, k := range keys { 127 for _, v := range r.Header[k] { 128 fmt.Fprintf(w, "%s: %s\n", k, v) 129 } 130 } 131 // Host isn't in r.Header 132 if r.Host != "" { 133 fmt.Fprintf(w, "Host: %s\n", r.Host) 134 } 135 } 136 137 // connResponse names two of its own response headers in Connection; 138 // a conforming proxy must strip both (RFC 9110 ยง7.6.1) but keep 139 // X-Keep-Resp. 140 func connResponse(w http.ResponseWriter, r *http.Request) { 141 setHeaders(w) 142 w.Header().Set("X-Hop-Before", "must-not-leak") 143 w.Header().Set("Connection", "X-Hop-Before, X-Hop-After") 144 w.Header().Set("X-Hop-After", "must-not-leak") 145 w.Header().Set("X-Keep-Resp", "survivor") 146 w.Header().Set("Content-Type", "text/plain") 147 w.WriteHeader(200) 148 fmt.Fprint(w, "conn\n") 149 } 150 151 func deleteItem(w http.ResponseWriter, r *http.Request) { 152 setHeaders(w) 153 w.WriteHeader(204) 154 } 155 156 func options(w http.ResponseWriter, r *http.Request) { 157 setHeaders(w) 158 w.Header().Set("Allow", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS") 159 w.WriteHeader(204) 160 } 161 162 func route(w http.ResponseWriter, r *http.Request) { 163 if r.Method == http.MethodOptions { 164 options(w, r) 165 return 166 } 167 switch { 168 case r.URL.Path == "/hello": 169 hello(w, r) 170 case strings.HasPrefix(r.URL.Path, "/status/"): 171 status(w, r) 172 case strings.HasPrefix(r.URL.Path, "/large/"): 173 large(w, r) 174 case strings.HasPrefix(r.URL.Path, "/slow/"): 175 slow(w, r) 176 case r.URL.Path == "/echo" && r.Method == http.MethodPost: 177 echo(w, r) 178 case r.URL.Path == "/upload" && r.Method == http.MethodPost: 179 upload(w, r, "Received") 180 case r.URL.Path == "/put" && r.Method == http.MethodPut: 181 upload(w, r, "PUT received") 182 case r.URL.Path == "/patch" && r.Method == http.MethodPatch: 183 upload(w, r, "PATCH received") 184 case strings.HasPrefix(r.URL.Path, "/item") && r.Method == http.MethodDelete: 185 deleteItem(w, r) 186 case r.URL.Path == "/echo-headers": 187 echoHeaders(w, r) 188 case r.URL.Path == "/conn-response": 189 connResponse(w, r) 190 default: 191 w.Header().Set("Content-Type", "text/plain") 192 w.WriteHeader(404) 193 fmt.Fprint(w, "not found\n") 194 } 195 } 196 197 func main() { 198 port := 8402 199 if len(os.Args) > 1 { 200 // ":0" would bind an ephemeral port while the driver waits 201 // for the one it asked for; ":garbage" would fail five 202 // seconds into that wait, blaming the wrong thing. 203 n, err := strconv.Atoi(os.Args[1]) 204 if err != nil || n < 1 || n > 65535 { 205 fmt.Fprintf(os.Stderr, "invalid port %q\n", os.Args[1]) 206 os.Exit(1) 207 } 208 port = n 209 } 210 // Loopback only: this server echoes an arbitrary POST body back 211 // and hands out 10 MiB on request, and has no business being 212 // reachable from the network for the duration of `make check'. 213 srv := &http.Server{ 214 Addr: fmt.Sprintf("127.0.0.1:%d", port), 215 Handler: http.HandlerFunc(route), 216 } 217 fmt.Fprintf(os.Stderr, "upstream_go listening on port %d\n", port) 218 if err := srv.ListenAndServe(); err != nil { 219 fmt.Fprintln(os.Stderr, err) 220 os.Exit(1) 221 } 222 }