paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

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 }