diff --git a/multiple-servers/IMPLEMENTATION.md b/multiple-servers/IMPLEMENTATION.md
new file mode 100644
index 000000000..dd268a679
--- /dev/null
+++ b/multiple-servers/IMPLEMENTATION.md
@@ -0,0 +1,240 @@
+This file contains notes on the implementation of this project, to serve as a guide for writing the README.md. It's in this file to avoid merge conflicts as the README.md is updated.
+
+# Implementation
+
+## Code organisation
+
+There are a few ways to organise this:
+
+- One module, one package with all servers implemented there
+- One module, multiple packages with each server
+
+And an open question about to start each server independently:
+
+- A top-level switch from CLI arguments
+- Multiple mains
+- `cmd` directory
+
+Read https://medium.com/@benbjohnson/structuring-applications-in-go-3b04be4ff091
+
+Initially I'll go with one module, multiple packages, and a `cmd` directory:
+
+```
+cmd/
+ static-server/
+ main.go
+ api-server/
+ main.go
+static/
+ static.go
+api/
+ api.go
+```
+
+Running will be `go run ./cmd/static-server`
+
+## Static files
+
+A self-contained website is in `assets`. This is just a simple image gallery that loads images from static configuration. Later on I'll update it to load from a URL.
+
+`cmd/static-server/main.go` accept CLI flag to assets directory, create config, pass to the server
+
+Server listens, reads files when it gets a request. Using `http.ServeFile` — works v well.
+
+## API
+
+Copied over from server-database, with file split up:
+
+- `util.go` for `MarshalWithIndent`
+- `images.go` for all images
+- `api.go` for the DB connection & HTTP handlers
+
+This has the same setup steps as server-database, so those can be copied over.
+
+## Ports
+
+`cmd` files should allow ports to be configred using `--port`.
+
+## Nginx
+
+Switch static server over to 8082.
+
+`brew install nginx`
+
+```
+nginx -c `pwd`/config/nginx.conf
+```
+
+## Benchmark
+
+`ab` the API:
+
+```console
+> ab -n 5000 -c 25 "http://127.0.0.1:8080/api/images.json"
+This is ApacheBench, Version 2.3 <$Revision: 1901567 $>
+Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
+Licensed to The Apache Software Foundation, http://www.apache.org/
+
+Benchmarking 127.0.0.1 (be patient)
+
+Completed 500 requests
+Completed 1000 requests
+Completed 1500 requests
+Completed 2000 requests
+Completed 2500 requests
+Completed 3000 requests
+Completed 3500 requests
+Completed 4000 requests
+Completed 4500 requests
+Completed 5000 requests
+Finished 5000 requests
+
+
+Server Software: nginx/1.23.1
+Server Hostname: 127.0.0.1
+Server Port: 8080
+
+Document Path: /api/images.json
+Document Length: 4 bytes
+
+Concurrency Level: 25
+Time taken for tests: 1.866 seconds
+Complete requests: 5000
+Failed requests: 0
+Total transferred: 885000 bytes
+HTML transferred: 20000 bytes
+Requests per second: 2680.08 [#/sec] (mean)
+Time per request: 9.328 [ms] (mean)
+Time per request: 0.373 [ms] (mean, across all concurrent requests)
+Transfer rate: 463.26 [Kbytes/sec] received
+
+Connection Times (ms)
+ min mean[+/-sd] median max
+Connect: 0 1 2.0 1 59
+Processing: 1 8 24.2 3 322
+Waiting: 0 7 20.3 3 322
+Total: 1 9 24.3 4 323
+
+Percentage of the requests served within a certain time (ms)
+ 50% 4
+ 66% 4
+ 75% 5
+ 80% 6
+ 90% 28
+ 95% 34
+ 98% 37
+ 99% 62
+ 100% 323 (longest request)
+```
+
+And the static server:
+
+```console
+> ab -n 5000 -c 25 "http://127.0.0.1:8080/"
+This is ApacheBench, Version 2.3 <$Revision: 1901567 $>
+Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
+Licensed to The Apache Software Foundation, http://www.apache.org/
+
+Benchmarking 127.0.0.1 (be patient)
+Completed 500 requests
+Completed 1000 requests
+Completed 1500 requests
+Completed 2000 requests
+Completed 2500 requests
+Completed 3000 requests
+Completed 3500 requests
+Completed 4000 requests
+Completed 4500 requests
+Completed 5000 requests
+Finished 5000 requests
+
+
+Server Software: nginx/1.23.1
+Server Hostname: 127.0.0.1
+Server Port: 8080
+
+Document Path: /
+Document Length: 607 bytes
+
+Concurrency Level: 25
+Time taken for tests: 1.502 seconds
+Complete requests: 5000
+Failed requests: 0
+Total transferred: 4165000 bytes
+HTML transferred: 3035000 bytes
+Requests per second: 3328.26 [#/sec] (mean)
+Time per request: 7.511 [ms] (mean)
+Time per request: 0.300 [ms] (mean, across all concurrent requests)
+Transfer rate: 2707.46 [Kbytes/sec] received
+
+Connection Times (ms)
+ min mean[+/-sd] median max
+Connect: 0 1 0.3 1 3
+Processing: 1 7 10.2 3 115
+Waiting: 1 6 9.2 3 115
+Total: 2 7 10.1 4 116
+
+Percentage of the requests served within a certain time (ms)
+ 50% 4
+ 66% 4
+ 75% 5
+ 80% 9
+ 90% 23
+ 95% 25
+ 98% 27
+ 99% 28
+ 100% 116 (longest request)
+```
+
+## Multiple backends
+
+```nginx
+http {
+ ...
+
+ # Define a group of API servers that nginx can use
+ upstream api {
+ server localhost:8081;
+ }
+
+ ...
+
+ proxy_pass http://api/;
+
+ ...
+}
+```
+
+Add alternatives:
+
+```nginx
+ server localhost:8083;
+ server localhost:8084;
+```
+
+Run them:
+
+```console
+> DATABASE_URL='postgres://localhost:5432/go-server-database' go run ./cmd/api-server --port 8083
+> DATABASE_URL='postgres://localhost:5432/go-server-database' go run ./cmd/api-server --port 8084
+```
+
+Run a small `ab` (`ab -n 10 -c 10 "http://127.0.0.1:8080/api/images.json"`) and observe the server logs: the requests are distributed between the servers.
+
+Turn off one of the servers and run another small `ab`: `ab -n 10 -c 10 "http://127.0.0.1:8080/api/images.json"`
+
+Look at the `nginx` logs:
+
+```
+127.0.0.1 - - [21/Aug/2022:17:07:44 +0100] "GET /api/images.json HTTP/1.0" 200 4 "-" "ApacheBench/2.3"
+2022/08/21 17:07:44 [error] 31112#0: *4088 kevent() reported that connect() failed (61: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://127.0.0.1:8084/images.json", host: "127.0.0.1:8080"
+2022/08/21 17:07:44 [warn] 31112#0: *4088 upstream server temporarily disabled while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://127.0.0.1:8084/images.json", host: "127.0.0.1:8080"
+2022/08/21 17:07:44 [error] 31112#0: *4090 kevent() reported that connect() failed (61: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://[::1]:8084/images.json", host: "127.0.0.1:8080"
+2022/08/21 17:07:44 [warn] 31112#0: *4090 upstream server temporarily disabled while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://[::1]:8084/images.json", host: "127.0.0.1:8080"
+2022/08/21 17:07:44 [error] 31113#0: *4092 kevent() reported that connect() failed (61: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://127.0.0.1:8084/images.json", host: "127.0.0.1:8080"
+2022/08/21 17:07:44 [warn] 31113#0: *4092 upstream server temporarily disabled while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://127.0.0.1:8084/images.json", host: "127.0.0.1:8080"
+2022/08/21 17:07:44 [error] 31113#0: *4092 kevent() reported that connect() failed (61: Connection refused) while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://[::1]:8084/images.json", host: "127.0.0.1:8080"
+2022/08/21 17:07:44 [warn] 31113#0: *4092 upstream server temporarily disabled while connecting to upstream, client: 127.0.0.1, server: , request: "GET /api/images.json HTTP/1.0", upstream: "http://[::1]:8084/images.json", host: "127.0.0.1:8080"
+```
+
+Note `upstream server temporarily disabled while connecting to upstream` — it is automatically spotting this and disabling the server.
diff --git a/multiple-servers/README.md b/multiple-servers/README.md
new file mode 100644
index 000000000..84d200ef0
--- /dev/null
+++ b/multiple-servers/README.md
@@ -0,0 +1,18 @@
+# Multiple servers
+
+
+
+Create file server to serve static HTML files. Create an API server that serves JSON from a database. Run the API and file server as two separate servers. Try to load the website & see CORS issue. Fix issues with `Access-Control-Allow-Origin`. Put apache in front of the file server and the API so they are on a single port and hostname. Learn about how to run services in VMs in the cloud. Replicate this local setup in the cloud on a single VM, with all services running on the same host. Route requests to the service.
+
+Timebox: 10 days
+
+Learning objectives:
+
+- Basic microservices ideas, separating concerns of services
+- Configure apache to talk to 2-3 copies of the API server
+- Some web security ideas (CORS)
+- Reverse proxy configuration, routing on path
+- Health checks
+- Running applications in the cloud on a raw VM
+- Using cloud-hosted services like databases
+- Multi-environment configuration
diff --git a/multiple-servers/api/api.go b/multiple-servers/api/api.go
new file mode 100644
index 000000000..b70a7c51a
--- /dev/null
+++ b/multiple-servers/api/api.go
@@ -0,0 +1,73 @@
+package api
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+
+ "github.com/jackc/pgx/v4"
+)
+
+type Config struct {
+ DatabaseURL string
+ Port int
+}
+
+func Run(config Config) error {
+ conn, err := pgx.Connect(context.Background(), config.DatabaseURL)
+ if err != nil {
+ return fmt.Errorf("unable to connect to database: %v", err)
+ }
+ // Defer closing the connection to when main function exits
+ defer conn.Close(context.Background())
+
+ http.HandleFunc("/images.json", func(w http.ResponseWriter, r *http.Request) {
+ log.Println(r.Method, r.URL.EscapedPath())
+
+ // Grab the indent query param early
+ indent := r.URL.Query().Get("indent")
+
+ var response []byte
+ var responseErr error
+ if r.Method == "POST" {
+ // Add new image to the database
+ image, err := AddImage(conn, r)
+ if err != nil {
+ log.Println(err.Error())
+ // We don't expose our internal errors (i.e. the contents of err) directly to the user for a few reasons:
+ // 1. It may leak private information (e.g. a database connection string, which may even include a password!), which may be a security risk.
+ // 2. It probably isn't useful to them to know.
+ // 3. It may contain confusing terminology which may be embarrassing or confusing to expose.
+ http.Error(w, "Something went wrong", http.StatusInternalServerError)
+ return
+ }
+
+ response, responseErr = MarshalWithIndent(image, indent)
+ } else {
+ // Fetch images from the database
+ images, err := FetchImages(conn)
+ if err != nil {
+ log.Println(err.Error())
+ http.Error(w, "Something went wrong", http.StatusInternalServerError)
+ return
+ }
+
+ response, responseErr = MarshalWithIndent(images, indent)
+ }
+
+ if responseErr != nil {
+ log.Println(responseErr.Error())
+ http.Error(w, "Something went wrong", http.StatusInternalServerError)
+ return
+ }
+ // Indicate that what follows will be JSON
+ w.Header().Add("Content-Type", "text/json")
+ w.Header().Add("Access-Control-Allow-Origin", "*")
+ // Send it back!
+ w.Write(response)
+ })
+
+ log.Printf("port: %d\n", config.Port)
+ return http.ListenAndServe(fmt.Sprintf(":%d", config.Port), nil)
+}
diff --git a/multiple-servers/api/images.go b/multiple-servers/api/images.go
new file mode 100644
index 000000000..552c80cc2
--- /dev/null
+++ b/multiple-servers/api/images.go
@@ -0,0 +1,69 @@
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/jackc/pgx/v4"
+)
+
+type Image struct {
+ Title string `json:"title"`
+ AltText string `json:"alt_text"`
+ URL string `json:"url"`
+}
+
+func (img Image) String() string {
+ return fmt.Sprintf("%s (%s): %s", img.Title, img.AltText, img.URL)
+}
+
+func FetchImages(conn *pgx.Conn) ([]Image, error) {
+ // Send a query to the database, returning raw rows
+ rows, err := conn.Query(context.Background(), "SELECT title, url, alt_text FROM public.images")
+ // Handle query errors
+ if err != nil {
+ return nil, fmt.Errorf("unable to query database: [%w]", err)
+ }
+
+ // Create slice to contain the images
+ var images []Image
+ // Iterate through each row to extract the data
+ for rows.Next() {
+ var title, url, altText string
+ // Extract the data, passing pointers so the data can be updated in place
+ err = rows.Scan(&title, &url, &altText)
+ if err != nil {
+ return nil, fmt.Errorf("unable to read from database: %w", err)
+ }
+ // Append this as a new Image to the images slice
+ images = append(images, Image{Title: title, URL: url, AltText: altText})
+ }
+
+ return images, nil
+}
+
+func AddImage(conn *pgx.Conn, r *http.Request) (*Image, error) {
+ // Read the request body into a bytes slice
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ return nil, fmt.Errorf("could not read request body: [%w]", err)
+ }
+
+ // Parse the body JSON into an image struct
+ var image Image
+ err = json.Unmarshal(body, &image)
+ if err != nil {
+ return nil, fmt.Errorf("could not parse request body: [%w]", err)
+ }
+
+ // Insert it into the database
+ _, err = conn.Exec(context.Background(), "INSERT INTO public.images(title, url, alt_text) VALUES ($1, $2, $3)", image.Title, image.URL, image.AltText)
+ if err != nil {
+ return nil, fmt.Errorf("could not insert image: [%w]", err)
+ }
+
+ return &image, nil
+}
diff --git a/multiple-servers/api/util.go b/multiple-servers/api/util.go
new file mode 100644
index 000000000..9420d26ae
--- /dev/null
+++ b/multiple-servers/api/util.go
@@ -0,0 +1,26 @@
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// The special type interface{} allows us to take _any_ value, not just one of a specific type.
+// This means we can re-use this function for both a single image _and_ a slice of multiple images.
+func MarshalWithIndent(data interface{}, indent string) ([]byte, error) {
+ // Convert images to a byte-array for writing back in a response
+ var b []byte
+ var marshalErr error
+ // Allow up to 10 characters of indent
+ if i, err := strconv.Atoi(indent); err == nil && i > 0 && i <= 10 {
+ b, marshalErr = json.MarshalIndent(data, "", strings.Repeat(" ", i))
+ } else {
+ b, marshalErr = json.Marshal(data)
+ }
+ if marshalErr != nil {
+ return nil, fmt.Errorf("could not marshal data: [%w]", marshalErr)
+ }
+ return b, nil
+}
diff --git a/multiple-servers/assets/index.html b/multiple-servers/assets/index.html
new file mode 100644
index 000000000..b3ab20978
--- /dev/null
+++ b/multiple-servers/assets/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Image gallery
+
+
+
+
+
+
+
Gallery
+
+ Sunsets and animals like you've never seen them before.
+
+
+ Loading images…
+
+
+
+
+
diff --git a/multiple-servers/assets/script.js b/multiple-servers/assets/script.js
new file mode 100644
index 000000000..ce58e4de9
--- /dev/null
+++ b/multiple-servers/assets/script.js
@@ -0,0 +1,28 @@
+function fetchImages() {
+ return fetch("http://localhost:8080/api/images.json").then(_ => _.json())
+ }
+
+ function timeout(t, v) {
+ return new Promise(res => {
+ setTimeout(() => res(v), t);
+ })
+ }
+
+ const gallery$ = document.querySelector(".gallery");
+
+ fetchImages().then(images => {
+ gallery$.textContent = images.length ? "" : "No images available.";
+
+ images.forEach(img => {
+ const imgElem$ = document.createElement("img");
+ imgElem$.src = img.URL;
+ imgElem$.alt = img.AltText;
+ const titleElem$ = document.createElement("h3");
+ titleElem$.textContent = img.Title;
+ const wrapperElem$ = document.createElement("div");
+ wrapperElem$.classList.add("gallery-image");
+ wrapperElem$.appendChild(titleElem$);
+ wrapperElem$.appendChild(imgElem$);
+ gallery$.appendChild(wrapperElem$);
+ });
+ });
\ No newline at end of file
diff --git a/multiple-servers/assets/style.css b/multiple-servers/assets/style.css
new file mode 100644
index 000000000..1f5112aa0
--- /dev/null
+++ b/multiple-servers/assets/style.css
@@ -0,0 +1,76 @@
+:root {
+ --color-bg: #565264;
+ --color-main: #ffffff;
+ --color-primary: #D6CFCB;
+ --color-secondary: #CCB7AE;
+ --color-tertiary: #706677;
+ --wrapper-height: 87vh;
+ --image-max-width: 300px;
+ --image-margin: 3rem;
+ --font-family: "HK Grotesk";
+ --font-family-header: "HK Grotesk";
+}
+
+/* Basic page style resets */
+* {
+ box-sizing: border-box;
+}
+[hidden] {
+ display: none !important;
+}
+
+img {
+ max-width: 100%;
+}
+
+/* Import fonts */
+@font-face {
+ font-family: HK Grotesk;
+ src: url("https://cdn.glitch.me/605e2a51-d45f-4d87-a285-9410ad350515%2FHKGrotesk-Regular.otf?v=1603136326027")
+ format("opentype");
+}
+@font-face {
+ font-family: HK Grotesk;
+ font-weight: bold;
+ src: url("https://cdn.glitch.me/605e2a51-d45f-4d87-a285-9410ad350515%2FHKGrotesk-Bold.otf?v=1603136323437")
+ format("opentype");
+}
+
+body {
+ font-family: HK Grotesk;
+ background-color: var(--color-bg);
+ color: var(--color-main);
+}
+
+/* Page structure */
+.wrapper {
+ min-height: var(--wrapper-height);
+ display: grid;
+ place-items: normal center;
+ margin: 0 1rem;
+}
+.content {
+ max-width: 1032px;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ justify-content: start;
+}
+
+h1 {
+ color: var(--color-primary);
+ font-style: normal;
+ font-weight: bold;
+ font-size: 100px;
+ line-height: 105%;
+ margin: 0;
+}
+
+h2 {
+ color: var(--color-secondary);
+}
+
+.gallery-image img {
+ border: 1em solid var(--color-tertiary);
+}
\ No newline at end of file
diff --git a/multiple-servers/cmd/api-server/main.go b/multiple-servers/cmd/api-server/main.go
new file mode 100644
index 000000000..90f3e033d
--- /dev/null
+++ b/multiple-servers/cmd/api-server/main.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "os"
+ "servers/api"
+)
+
+func main() {
+ // Check that DATABASE_URL is set
+ if os.Getenv("DATABASE_URL") == "" {
+ log.Fatalln("DATABASE_URL not set")
+ }
+
+ port := flag.Int("port", 8081, "port the server will listen on")
+ flag.Parse()
+
+ log.Fatal(api.Run(api.Config{
+ DatabaseURL: os.Getenv("DATABASE_URL"),
+ Port: *port,
+ }))
+}
diff --git a/multiple-servers/cmd/static-server/main.go b/multiple-servers/cmd/static-server/main.go
new file mode 100644
index 000000000..3fa195137
--- /dev/null
+++ b/multiple-servers/cmd/static-server/main.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "path/filepath"
+ "servers/static"
+)
+
+func main() {
+ path := flag.String("path", ".", "path to static files")
+ port := flag.Int("port", 8082, "port the server will listen on")
+ flag.Parse()
+
+ absPath, err := filepath.Abs(*path)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ log.Fatal(static.Run(static.Config{
+ Dir: absPath,
+ Port: *port,
+ }))
+}
diff --git a/multiple-servers/config/httpd.conf b/multiple-servers/config/httpd.conf
new file mode 100644
index 000000000..253aaafc3
--- /dev/null
+++ b/multiple-servers/config/httpd.conf
@@ -0,0 +1,460 @@
+#
+# This is the main Apache HTTP server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See for detailed information.
+# In particular, see
+#
+# for a discussion of each configuration directive.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path. If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so "logs/access_log"
+# with ServerRoot set to "/usr/local/apache2" will be interpreted by the
+# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log"
+# will be interpreted as '/logs/access_log'.
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# Do not add a slash at the end of the directory path. If you point
+# ServerRoot at a non-local disk, be sure to specify a local disk on the
+# Mutex directive, if file-based mutexes are used. If you wish to share the
+# same ServerRoot for multiple httpd daemons, you will need to change at
+# least PidFile.
+#
+ServerRoot "/usr/local/opt/httpd"
+
+#
+# Mutex: Allows you to set the mutex mechanism and mutex file directory
+# for individual mutexes, or change the global defaults
+#
+# Uncomment and change the directory if mutexes are file-based and the default
+# mutex file directory is not on a local disk or is not appropriate for some
+# other reason.
+#
+# Mutex default:/usr/local/var/run/httpd
+
+#
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, instead of the default. See also the
+# directive.
+#
+# Change this to Listen on specific IP addresses as shown below to
+# prevent Apache from glomming onto all bound IP addresses.
+#
+#Listen 12.34.56.78:80
+Listen 8080
+
+#
+# Dynamic Shared Object (DSO) Support
+#
+# To be able to use the functionality of a module which was built as a DSO you
+# have to place corresponding `LoadModule' lines at this location so the
+# directives contained in it are actually available _before_ they are used.
+# Statically compiled modules (those listed by `httpd -l') do not need
+# to be loaded here.
+#
+# Example:
+# LoadModule foo_module modules/mod_foo.so
+#
+#LoadModule mpm_event_module lib/httpd/modules/mod_mpm_event.so
+LoadModule mpm_prefork_module lib/httpd/modules/mod_mpm_prefork.so
+#LoadModule mpm_worker_module lib/httpd/modules/mod_mpm_worker.so
+LoadModule authn_file_module lib/httpd/modules/mod_authn_file.so
+#LoadModule authn_dbm_module lib/httpd/modules/mod_authn_dbm.so
+#LoadModule authn_anon_module lib/httpd/modules/mod_authn_anon.so
+#LoadModule authn_dbd_module lib/httpd/modules/mod_authn_dbd.so
+#LoadModule authn_socache_module lib/httpd/modules/mod_authn_socache.so
+LoadModule authn_core_module lib/httpd/modules/mod_authn_core.so
+LoadModule authz_host_module lib/httpd/modules/mod_authz_host.so
+LoadModule authz_groupfile_module lib/httpd/modules/mod_authz_groupfile.so
+LoadModule authz_user_module lib/httpd/modules/mod_authz_user.so
+#LoadModule authz_dbm_module lib/httpd/modules/mod_authz_dbm.so
+#LoadModule authz_owner_module lib/httpd/modules/mod_authz_owner.so
+#LoadModule authz_dbd_module lib/httpd/modules/mod_authz_dbd.so
+LoadModule authz_core_module lib/httpd/modules/mod_authz_core.so
+#LoadModule authnz_fcgi_module lib/httpd/modules/mod_authnz_fcgi.so
+LoadModule access_compat_module lib/httpd/modules/mod_access_compat.so
+LoadModule auth_basic_module lib/httpd/modules/mod_auth_basic.so
+#LoadModule auth_form_module lib/httpd/modules/mod_auth_form.so
+#LoadModule auth_digest_module lib/httpd/modules/mod_auth_digest.so
+#LoadModule allowmethods_module lib/httpd/modules/mod_allowmethods.so
+#LoadModule file_cache_module lib/httpd/modules/mod_file_cache.so
+#LoadModule cache_module lib/httpd/modules/mod_cache.so
+#LoadModule cache_disk_module lib/httpd/modules/mod_cache_disk.so
+#LoadModule cache_socache_module lib/httpd/modules/mod_cache_socache.so
+#LoadModule socache_shmcb_module lib/httpd/modules/mod_socache_shmcb.so
+#LoadModule socache_dbm_module lib/httpd/modules/mod_socache_dbm.so
+#LoadModule socache_memcache_module lib/httpd/modules/mod_socache_memcache.so
+#LoadModule socache_redis_module lib/httpd/modules/mod_socache_redis.so
+#LoadModule watchdog_module lib/httpd/modules/mod_watchdog.so
+#LoadModule macro_module lib/httpd/modules/mod_macro.so
+#LoadModule dbd_module lib/httpd/modules/mod_dbd.so
+#LoadModule dumpio_module lib/httpd/modules/mod_dumpio.so
+#LoadModule echo_module lib/httpd/modules/mod_echo.so
+#LoadModule buffer_module lib/httpd/modules/mod_buffer.so
+#LoadModule data_module lib/httpd/modules/mod_data.so
+#LoadModule ratelimit_module lib/httpd/modules/mod_ratelimit.so
+LoadModule reqtimeout_module lib/httpd/modules/mod_reqtimeout.so
+#LoadModule ext_filter_module lib/httpd/modules/mod_ext_filter.so
+#LoadModule request_module lib/httpd/modules/mod_request.so
+#LoadModule include_module lib/httpd/modules/mod_include.so
+LoadModule filter_module lib/httpd/modules/mod_filter.so
+#LoadModule reflector_module lib/httpd/modules/mod_reflector.so
+#LoadModule substitute_module lib/httpd/modules/mod_substitute.so
+#LoadModule sed_module lib/httpd/modules/mod_sed.so
+#LoadModule charset_lite_module lib/httpd/modules/mod_charset_lite.so
+#LoadModule deflate_module lib/httpd/modules/mod_deflate.so
+#LoadModule xml2enc_module lib/httpd/modules/mod_xml2enc.so
+#LoadModule proxy_html_module lib/httpd/modules/mod_proxy_html.so
+#LoadModule brotli_module lib/httpd/modules/mod_brotli.so
+LoadModule mime_module lib/httpd/modules/mod_mime.so
+LoadModule log_config_module lib/httpd/modules/mod_log_config.so
+#LoadModule log_debug_module lib/httpd/modules/mod_log_debug.so
+#LoadModule log_forensic_module lib/httpd/modules/mod_log_forensic.so
+#LoadModule logio_module lib/httpd/modules/mod_logio.so
+LoadModule env_module lib/httpd/modules/mod_env.so
+#LoadModule mime_magic_module lib/httpd/modules/mod_mime_magic.so
+#LoadModule expires_module lib/httpd/modules/mod_expires.so
+LoadModule headers_module lib/httpd/modules/mod_headers.so
+#LoadModule usertrack_module lib/httpd/modules/mod_usertrack.so
+#LoadModule unique_id_module lib/httpd/modules/mod_unique_id.so
+LoadModule setenvif_module lib/httpd/modules/mod_setenvif.so
+LoadModule version_module lib/httpd/modules/mod_version.so
+#LoadModule remoteip_module lib/httpd/modules/mod_remoteip.so
+LoadModule proxy_module lib/httpd/modules/mod_proxy.so
+#LoadModule proxy_connect_module lib/httpd/modules/mod_proxy_connect.so
+#LoadModule proxy_ftp_module lib/httpd/modules/mod_proxy_ftp.so
+LoadModule proxy_http_module lib/httpd/modules/mod_proxy_http.so
+#LoadModule proxy_fcgi_module lib/httpd/modules/mod_proxy_fcgi.so
+#LoadModule proxy_scgi_module lib/httpd/modules/mod_proxy_scgi.so
+#LoadModule proxy_uwsgi_module lib/httpd/modules/mod_proxy_uwsgi.so
+#LoadModule proxy_fdpass_module lib/httpd/modules/mod_proxy_fdpass.so
+#LoadModule proxy_wstunnel_module lib/httpd/modules/mod_proxy_wstunnel.so
+#LoadModule proxy_ajp_module lib/httpd/modules/mod_proxy_ajp.so
+#LoadModule proxy_balancer_module lib/httpd/modules/mod_proxy_balancer.so
+#LoadModule proxy_express_module lib/httpd/modules/mod_proxy_express.so
+#LoadModule proxy_hcheck_module lib/httpd/modules/mod_proxy_hcheck.so
+#LoadModule session_module lib/httpd/modules/mod_session.so
+#LoadModule session_cookie_module lib/httpd/modules/mod_session_cookie.so
+#LoadModule session_crypto_module lib/httpd/modules/mod_session_crypto.so
+#LoadModule session_dbd_module lib/httpd/modules/mod_session_dbd.so
+#LoadModule slotmem_shm_module lib/httpd/modules/mod_slotmem_shm.so
+#LoadModule slotmem_plain_module lib/httpd/modules/mod_slotmem_plain.so
+#LoadModule ssl_module lib/httpd/modules/mod_ssl.so
+#LoadModule dialup_module lib/httpd/modules/mod_dialup.so
+#LoadModule http2_module lib/httpd/modules/mod_http2.so
+#LoadModule lbmethod_byrequests_module lib/httpd/modules/mod_lbmethod_byrequests.so
+#LoadModule lbmethod_bytraffic_module lib/httpd/modules/mod_lbmethod_bytraffic.so
+#LoadModule lbmethod_bybusyness_module lib/httpd/modules/mod_lbmethod_bybusyness.so
+#LoadModule lbmethod_heartbeat_module lib/httpd/modules/mod_lbmethod_heartbeat.so
+LoadModule unixd_module lib/httpd/modules/mod_unixd.so
+#LoadModule heartbeat_module lib/httpd/modules/mod_heartbeat.so
+#LoadModule heartmonitor_module lib/httpd/modules/mod_heartmonitor.so
+#LoadModule dav_module lib/httpd/modules/mod_dav.so
+LoadModule status_module lib/httpd/modules/mod_status.so
+LoadModule autoindex_module lib/httpd/modules/mod_autoindex.so
+#LoadModule asis_module lib/httpd/modules/mod_asis.so
+#LoadModule info_module lib/httpd/modules/mod_info.so
+#LoadModule suexec_module lib/httpd/modules/mod_suexec.so
+
+ #LoadModule cgid_module lib/httpd/modules/mod_cgid.so
+
+
+ #LoadModule cgi_module lib/httpd/modules/mod_cgi.so
+
+#LoadModule dav_fs_module lib/httpd/modules/mod_dav_fs.so
+#LoadModule dav_lock_module lib/httpd/modules/mod_dav_lock.so
+#LoadModule vhost_alias_module lib/httpd/modules/mod_vhost_alias.so
+#LoadModule negotiation_module lib/httpd/modules/mod_negotiation.so
+LoadModule dir_module lib/httpd/modules/mod_dir.so
+#LoadModule actions_module lib/httpd/modules/mod_actions.so
+#LoadModule speling_module lib/httpd/modules/mod_speling.so
+#LoadModule userdir_module lib/httpd/modules/mod_userdir.so
+LoadModule alias_module lib/httpd/modules/mod_alias.so
+#LoadModule rewrite_module lib/httpd/modules/mod_rewrite.so
+
+
+#
+# If you wish httpd to run as a different user or group, you must run
+# httpd as root initially and it will switch.
+#
+# User/Group: The name (or #number) of the user/group to run httpd as.
+# It is usually good practice to create a dedicated user and group for
+# running httpd, as with most system services.
+#
+User _www
+Group _www
+
+
+
+# 'Main' server configuration
+#
+# The directives in this section set up the values used by the 'main'
+# server, which responds to any requests that aren't handled by a
+# definition. These values also provide defaults for
+# any containers you may define later in the file.
+#
+# All of these directives may appear inside containers,
+# in which case these default settings will be overridden for the
+# virtual host being defined.
+#
+
+#
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed. This address appears on some server-generated pages, such
+# as error documents. e.g. admin@your-domain.com
+#
+ServerAdmin you@example.com
+
+#
+# ServerName gives the name and port that the server uses to identify itself.
+# This can often be determined automatically, but we recommend you specify
+# it explicitly to prevent problems during startup.
+#
+# If your host doesn't have a registered DNS name, enter its IP address here.
+#
+ServerName localhost:8080
+
+#
+# Deny access to the entirety of your server's filesystem. You must
+# explicitly permit access to web content directories in other
+# blocks below.
+#
+
+ AllowOverride none
+ Require all denied
+
+
+#
+# Note that from this point forward you must specifically allow
+# particular features to be enabled - so if something's not working as
+# you might expect, make sure that you have specifically enabled it
+# below.
+#
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+
+ Require all denied
+
+
+#
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog "/usr/local/var/log/httpd/error_log"
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel warn
+
+
+ #
+ # The following directives define some format nicknames for use with
+ # a CustomLog directive (see below).
+ #
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+
+ # You need to enable mod_logio.c to use %I and %O
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+
+
+ #
+ # The location and format of the access logfile (Common Logfile Format).
+ # If you do not define any access logfiles within a
+ # container, they will be logged here. Contrariwise, if you *do*
+ # define per- access logfiles, transactions will be
+ # logged therein and *not* in this file.
+ #
+ CustomLog "/usr/local/var/log/httpd/access_log" common
+
+ #
+ # If you prefer a logfile with access, agent, and referer information
+ # (Combined Logfile Format) you can use the following directive.
+ #
+ #CustomLog "/usr/local/var/log/httpd/access_log" combined
+
+
+
+ #
+ # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied
+ # backend servers which have lingering "httpoxy" defects.
+ # 'Proxy' request header is undefined by the IETF, not listed by IANA
+ #
+ RequestHeader unset Proxy early
+
+
+
+ #
+ # TypesConfig points to the file containing the list of mappings from
+ # filename extension to MIME-type.
+ #
+ TypesConfig /usr/local/etc/httpd/mime.types
+
+ #
+ # AddType allows you to add to or override the MIME configuration
+ # file specified in TypesConfig for specific file types.
+ #
+ #AddType application/x-gzip .tgz
+ #
+ # AddEncoding allows you to have certain browsers uncompress
+ # information on the fly. Note: Not all browsers support this.
+ #
+ #AddEncoding x-compress .Z
+ #AddEncoding x-gzip .gz .tgz
+ #
+ # If the AddEncoding directives above are commented-out, then you
+ # probably should define those extensions to indicate media types:
+ #
+ AddType application/x-compress .Z
+ AddType application/x-gzip .gz .tgz
+
+ #
+ # AddHandler allows you to map certain file extensions to "handlers":
+ # actions unrelated to filetype. These can be either built into the server
+ # or added with the Action directive (see below)
+ #
+ # To use CGI scripts outside of ScriptAliased directories:
+ # (You will also need to add "ExecCGI" to the "Options" directive.)
+ #
+ #AddHandler cgi-script .cgi
+
+ # For type maps (negotiated resources):
+ #AddHandler type-map var
+
+ #
+ # Filters allow you to process content before it is sent to the client.
+ #
+ # To parse .shtml files for server-side includes (SSI):
+ # (You will also need to add "Includes" to the "Options" directive.)
+ #
+ #AddType text/html .shtml
+ #AddOutputFilter INCLUDES .shtml
+
+
+#
+# The mod_mime_magic module allows the server to use various hints from the
+# contents of the file itself to determine its type. The MIMEMagicFile
+# directive tells the module where the hint definitions are located.
+#
+#MIMEMagicFile /usr/local/etc/httpd/magic
+
+#
+# Customizable error responses come in three flavors:
+# 1) plain text 2) local redirects 3) external redirects
+#
+# Some examples:
+#ErrorDocument 500 "The server made a boo boo."
+#ErrorDocument 404 /missing.html
+#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
+#ErrorDocument 402 http://www.example.com/subscription_info.html
+#
+
+#
+# MaxRanges: Maximum number of Ranges in a request before
+# returning the entire resource, or one of the special
+# values 'default', 'none' or 'unlimited'.
+# Default setting is to accept 200 Ranges.
+#MaxRanges unlimited
+
+#
+# EnableMMAP and EnableSendfile: On systems that support it,
+# memory-mapping or the sendfile syscall may be used to deliver
+# files. This usually improves server performance, but must
+# be turned off when serving from networked-mounted
+# filesystems or if support for these functions is otherwise
+# broken on your system.
+# Defaults: EnableMMAP On, EnableSendfile Off
+#
+#EnableMMAP off
+#EnableSendfile on
+
+# Supplemental configuration
+#
+# The configuration files in the /usr/local/etc/httpd/extra/ directory can be
+# included to add extra features or to modify the default configuration of
+# the server, or you may simply copy their contents here and change as
+# necessary.
+
+# Server-pool management (MPM specific)
+#Include /usr/local/etc/httpd/extra/httpd-mpm.conf
+
+# Multi-language error messages
+#Include /usr/local/etc/httpd/extra/httpd-multilang-errordoc.conf
+
+# Fancy directory listings
+#Include /usr/local/etc/httpd/extra/httpd-autoindex.conf
+
+# Language settings
+#Include /usr/local/etc/httpd/extra/httpd-languages.conf
+
+# User home directories
+#Include /usr/local/etc/httpd/extra/httpd-userdir.conf
+
+# Real-time info on requests and configuration
+#Include /usr/local/etc/httpd/extra/httpd-info.conf
+
+# Virtual hosts
+#Include /usr/local/etc/httpd/extra/httpd-vhosts.conf
+
+# Local access to the Apache HTTP Server Manual
+#Include /usr/local/etc/httpd/extra/httpd-manual.conf
+
+# Distributed authoring and versioning (WebDAV)
+#Include /usr/local/etc/httpd/extra/httpd-dav.conf
+
+# Various default settings
+#Include /usr/local/etc/httpd/extra/httpd-default.conf
+
+# Configure mod_proxy_html to understand HTML4/XHTML1
+
+Include /usr/local/etc/httpd/extra/proxy-html.conf
+
+
+# Secure (SSL/TLS) connections
+#Include /usr/local/etc/httpd/extra/httpd-ssl.conf
+#
+# Note: The following must must be present to support
+# starting without SSL on platforms with no /dev/random equivalent
+# but a statically compiled-in mod_ssl.
+#
+
+SSLRandomSeed startup builtin
+SSLRandomSeed connect builtin
+
+
+# Proxy Config
+
+ ProxyPreserveHost On
+
+ # Servers to proxy the connection, or;
+ # List of application servers:
+ # Usage:
+ # ProxyPass / http://[IP Addr.]:[port]/
+ # ProxyPassReverse / http://[IP Addr.]:[port]/
+ # Example:
+ ProxyPass /api/ http://0.0.0.0:8081/
+ ProxyPassReverse /api/ http://0.0.0.0:8081/
+
+ ProxyPass / http://0.0.0.0:8082/
+ ProxyPassReverse / http://0.0.0.0:8082/
+
+ ServerName localhost
+
\ No newline at end of file
diff --git a/multiple-servers/config/mime.types b/multiple-servers/config/mime.types
new file mode 100644
index 000000000..1c00d701a
--- /dev/null
+++ b/multiple-servers/config/mime.types
@@ -0,0 +1,99 @@
+
+types {
+ text/html html htm shtml;
+ text/css css;
+ text/xml xml;
+ image/gif gif;
+ image/jpeg jpeg jpg;
+ application/javascript js;
+ application/atom+xml atom;
+ application/rss+xml rss;
+
+ text/mathml mml;
+ text/plain txt;
+ text/vnd.sun.j2me.app-descriptor jad;
+ text/vnd.wap.wml wml;
+ text/x-component htc;
+
+ image/avif avif;
+ image/png png;
+ image/svg+xml svg svgz;
+ image/tiff tif tiff;
+ image/vnd.wap.wbmp wbmp;
+ image/webp webp;
+ image/x-icon ico;
+ image/x-jng jng;
+ image/x-ms-bmp bmp;
+
+ font/woff woff;
+ font/woff2 woff2;
+
+ application/java-archive jar war ear;
+ application/json json;
+ application/mac-binhex40 hqx;
+ application/msword doc;
+ application/pdf pdf;
+ application/postscript ps eps ai;
+ application/rtf rtf;
+ application/vnd.apple.mpegurl m3u8;
+ application/vnd.google-earth.kml+xml kml;
+ application/vnd.google-earth.kmz kmz;
+ application/vnd.ms-excel xls;
+ application/vnd.ms-fontobject eot;
+ application/vnd.ms-powerpoint ppt;
+ application/vnd.oasis.opendocument.graphics odg;
+ application/vnd.oasis.opendocument.presentation odp;
+ application/vnd.oasis.opendocument.spreadsheet ods;
+ application/vnd.oasis.opendocument.text odt;
+ application/vnd.openxmlformats-officedocument.presentationml.presentation
+ pptx;
+ application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
+ xlsx;
+ application/vnd.openxmlformats-officedocument.wordprocessingml.document
+ docx;
+ application/vnd.wap.wmlc wmlc;
+ application/wasm wasm;
+ application/x-7z-compressed 7z;
+ application/x-cocoa cco;
+ application/x-java-archive-diff jardiff;
+ application/x-java-jnlp-file jnlp;
+ application/x-makeself run;
+ application/x-perl pl pm;
+ application/x-pilot prc pdb;
+ application/x-rar-compressed rar;
+ application/x-redhat-package-manager rpm;
+ application/x-sea sea;
+ application/x-shockwave-flash swf;
+ application/x-stuffit sit;
+ application/x-tcl tcl tk;
+ application/x-x509-ca-cert der pem crt;
+ application/x-xpinstall xpi;
+ application/xhtml+xml xhtml;
+ application/xspf+xml xspf;
+ application/zip zip;
+
+ application/octet-stream bin exe dll;
+ application/octet-stream deb;
+ application/octet-stream dmg;
+ application/octet-stream iso img;
+ application/octet-stream msi msp msm;
+
+ audio/midi mid midi kar;
+ audio/mpeg mp3;
+ audio/ogg ogg;
+ audio/x-m4a m4a;
+ audio/x-realaudio ra;
+
+ video/3gpp 3gpp 3gp;
+ video/mp2t ts;
+ video/mp4 mp4;
+ video/mpeg mpeg mpg;
+ video/quicktime mov;
+ video/webm webm;
+ video/x-flv flv;
+ video/x-m4v m4v;
+ video/x-mng mng;
+ video/x-ms-asf asx asf;
+ video/x-ms-wmv wmv;
+ video/x-msvideo avi;
+}
diff --git a/multiple-servers/config/nginx.conf b/multiple-servers/config/nginx.conf
new file mode 100644
index 000000000..1a1c1f76a
--- /dev/null
+++ b/multiple-servers/config/nginx.conf
@@ -0,0 +1,75 @@
+# Determines whether nginx should become a daemon (run in the background — daemon – or foreground)
+# https://nginx.org/en/docs/ngx_core_module.html#daemon
+daemon off;
+
+# For development purposes, log to stderr
+# https://nginx.org/en/docs/ngx_core_module.html#error_log
+error_log stderr info;
+
+# Defines the number of worker processes. Auto tries to optimise this, likely to the number of CPU cores.
+# https://nginx.org/en/docs/ngx_core_module.html#worker_processes
+worker_processes auto;
+
+# Directives that affect connection processing.
+# https://nginx.org/en/docs/ngx_core_module.html#events
+events {
+ # Sets the maximum number of simultaneous connections that can be opened by a worker process.
+ # https://nginx.org/en/docs/ngx_core_module.html#events
+ worker_connections 1024;
+}
+
+http {
+ include mime.types;
+
+ # Defines the default MIME type of a response.
+ # https://nginx.org/en/docs/http/ngx_http_core_module.html#default_type
+ default_type text/plain;
+
+ # Log to stdout
+ # https://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
+ access_log /dev/stdout;
+
+ # Specifies log format.
+ # https://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ # By default, NGINX handles file transmission itself and copies the file into the buffer before sending it.
+ # Enabling the sendfile directive eliminates the step of copying the data into the buffer and enables direct
+ # copying data from one file descriptor to another.
+ # https://docs.nginx.com/nginx/admin-guide/web-server/serving-static-content/
+ sendfile on;
+
+ # Enable compression
+ # https://docs.nginx.com/nginx/admin-guide/web-server/compression/
+ gzip on;
+
+ # Define a group of API servers that nginx can use
+ upstream api {
+ server localhost:8081;
+ # server localhost:8083;
+ # server localhost:8084;
+ }
+
+ # Sets configuration for a virtual server.
+ # https://nginx.org/en/docs/http/ngx_http_core_module.html#server
+ server {
+ # Port to listen on
+ listen 8080;
+
+ # Requests to /api/ are forwarded to a local server running on port 8081
+ # https://nginx.org/en/docs/http/ngx_http_core_module.html#location
+ location /api/ {
+ # Set URL to which the request is passed.
+ # In this case, pass to the "api" upstream.
+ # https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
+ proxy_pass http://api/;
+ }
+
+ # Other request forwarded to a local server running on port 8082
+ location / {
+ proxy_pass http://0.0.0.0:8082/;
+ }
+ }
+}
diff --git a/multiple-servers/go.mod b/multiple-servers/go.mod
new file mode 100644
index 000000000..38504f6e0
--- /dev/null
+++ b/multiple-servers/go.mod
@@ -0,0 +1,16 @@
+module servers
+
+go 1.18
+
+require (
+ github.com/jackc/chunkreader/v2 v2.0.1 // indirect
+ github.com/jackc/pgconn v1.13.0 // indirect
+ github.com/jackc/pgio v1.0.0 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgproto3/v2 v2.3.1 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
+ github.com/jackc/pgtype v1.12.0 // indirect
+ github.com/jackc/pgx/v4 v4.17.0 // indirect
+ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
+ golang.org/x/text v0.3.7 // indirect
+)
diff --git a/multiple-servers/go.sum b/multiple-servers/go.sum
new file mode 100644
index 000000000..066093dc3
--- /dev/null
+++ b/multiple-servers/go.sum
@@ -0,0 +1,175 @@
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
+github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
+github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
+github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
+github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.17.0 h1:Hsx+baY8/zU2WtPLQyZi8WbecgcsWEeyoK1jvg/WgIo=
+github.com/jackc/pgx/v4 v4.17.0/go.mod h1:Gd6RmOhtFLTu8cp/Fhq4kP195KrshxYJH3oW8AWJ1pw=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
diff --git a/multiple-servers/readme-assets/architecture.png b/multiple-servers/readme-assets/architecture.png
new file mode 100644
index 000000000..7e623c5ff
Binary files /dev/null and b/multiple-servers/readme-assets/architecture.png differ
diff --git a/multiple-servers/static/static.go b/multiple-servers/static/static.go
new file mode 100644
index 000000000..757890fdb
--- /dev/null
+++ b/multiple-servers/static/static.go
@@ -0,0 +1,28 @@
+package static
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+ "path/filepath"
+)
+
+type Config struct {
+ Dir string
+ Port int
+}
+
+func Run(config Config) error {
+ // The "/" path handles everything, so we need to inspect the path (`req.URL.Path`) to be able to
+ // identify which file to serve.
+ // https://pkg.go.dev/net/http#ServeMux.Handle
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ // Build a full absolute path to the file, relative to the config.Dir
+ path := filepath.Join(config.Dir, r.URL.EscapedPath())
+ log.Println(r.Method, r.URL.EscapedPath(), path)
+ http.ServeFile(w, r, path)
+ })
+
+ log.Printf("port: %d\n", config.Port)
+ return http.ListenAndServe(fmt.Sprintf(":%d", config.Port), nil)
+}