upstream_go.go (4900B)
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 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 General Public License for more details. 14 15 You should have received a copy of the GNU 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 func deleteItem(w http.ResponseWriter, r *http.Request) { 138 setHeaders(w) 139 w.WriteHeader(204) 140 } 141 142 func options(w http.ResponseWriter, r *http.Request) { 143 setHeaders(w) 144 w.Header().Set("Allow", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS") 145 w.WriteHeader(204) 146 } 147 148 func route(w http.ResponseWriter, r *http.Request) { 149 if r.Method == http.MethodOptions { 150 options(w, r) 151 return 152 } 153 switch { 154 case r.URL.Path == "/hello": 155 hello(w, r) 156 case strings.HasPrefix(r.URL.Path, "/status/"): 157 status(w, r) 158 case strings.HasPrefix(r.URL.Path, "/large/"): 159 large(w, r) 160 case strings.HasPrefix(r.URL.Path, "/slow/"): 161 slow(w, r) 162 case r.URL.Path == "/echo" && r.Method == http.MethodPost: 163 echo(w, r) 164 case r.URL.Path == "/upload" && r.Method == http.MethodPost: 165 upload(w, r, "Received") 166 case r.URL.Path == "/put" && r.Method == http.MethodPut: 167 upload(w, r, "PUT received") 168 case r.URL.Path == "/patch" && r.Method == http.MethodPatch: 169 upload(w, r, "PATCH received") 170 case strings.HasPrefix(r.URL.Path, "/item") && r.Method == http.MethodDelete: 171 deleteItem(w, r) 172 case r.URL.Path == "/echo-headers": 173 echoHeaders(w, r) 174 default: 175 w.Header().Set("Content-Type", "text/plain") 176 w.WriteHeader(404) 177 fmt.Fprint(w, "not found\n") 178 } 179 } 180 181 func main() { 182 port := "8402" 183 if len(os.Args) > 1 { 184 port = os.Args[1] 185 } 186 srv := &http.Server{ 187 Addr: ":" + port, 188 Handler: http.HandlerFunc(route), 189 } 190 fmt.Fprintf(os.Stderr, "upstream_go listening on port %s\n", port) 191 if err := srv.ListenAndServe(); err != nil { 192 fmt.Fprintln(os.Stderr, err) 193 os.Exit(1) 194 } 195 }