Skip to content
This repository was archived by the owner on Dec 28, 2024. It is now read-only.

Commit 94f794c

Browse files
committed
Break out HTTP server part of common to common/web
1 parent e682a59 commit 94f794c

File tree

5 files changed

+178
-130
lines changed

5 files changed

+178
-130
lines changed

common/setup.go

Lines changed: 6 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,21 @@ package common
22

33
import (
44
"context"
5-
"errors"
6-
"fmt"
7-
"io"
5+
"github.com/terminalnode/adventofcode2024/common/util"
6+
"github.com/terminalnode/adventofcode2024/common/web"
87
"log"
9-
"net/http"
108
"os"
119
"os/signal"
1210
"syscall"
1311
"time"
1412
)
1513

16-
type Solution = func(string) string
17-
1814
func Setup(
1915
day int,
20-
part1 Solution,
21-
part2 Solution,
16+
part1 util.Solution,
17+
part2 util.Solution,
2218
) {
23-
prefix := os.Getenv("AOC2024_PREFIX")
24-
http.HandleFunc(addPrefix(prefix, "/1"), createSolutionHandler(day, 1, part1))
25-
http.HandleFunc(addPrefix(prefix, "/2"), createSolutionHandler(day, 2, part2))
26-
http.HandleFunc(addPrefix(prefix, "/health"), healthCheckHandler)
27-
http.HandleFunc(addPrefix(prefix, "/health/live"), healthCheckHandler)
28-
http.HandleFunc(addPrefix(prefix, "/health/ready"), healthCheckHandler)
29-
30-
if prefix != "" {
31-
// For health endpoints, add non-prefixed handlers as well
32-
http.HandleFunc("/health", healthCheckHandler)
33-
http.HandleFunc("/health/live", healthCheckHandler)
34-
http.HandleFunc("/health/ready", healthCheckHandler)
35-
}
36-
37-
http.HandleFunc("/", unknownPathHandler)
38-
39-
// Run the HTTP server on port 8080 in the background
40-
server := &http.Server{Addr: ":8080", Handler: nil}
41-
go func() {
42-
log.Printf("Starting Day #%d service on port 8080", day)
43-
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
44-
log.Fatalf("Fatal server error: %v", err)
45-
}
46-
}()
19+
httpServer := web.CreateHttpServer(day, part1, part2)
4720

4821
// Open a signal channel, listening for SIGTERM and SIGINT
4922
signalChan := make(chan os.Signal, 1)
@@ -52,106 +25,9 @@ func Setup(
5225

5326
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
5427
defer cancel()
55-
if err := server.Shutdown(ctx); err != nil {
28+
if err := httpServer.Shutdown(ctx); err != nil {
5629
log.Fatalf("Server forced to shutdown: %v", err)
5730
}
5831

5932
log.Println("Server exited")
6033
}
61-
62-
func addPrefix(prefix string, url string) string {
63-
if prefix == "" {
64-
return url
65-
}
66-
return fmt.Sprintf("/%s%s", prefix, url)
67-
}
68-
69-
func createSolutionHandler(
70-
day int,
71-
part int,
72-
solution func(string) string,
73-
) func(http.ResponseWriter, *http.Request) {
74-
if solution == nil {
75-
solution = defaultSolutionHandler(day, part)
76-
}
77-
78-
return func(w http.ResponseWriter, r *http.Request) {
79-
if err := whitelistMethods([]string{"POST"}, w, r); err != nil {
80-
fmt.Print(err.Error())
81-
return
82-
}
83-
84-
input, err := readInput(r)
85-
if err != nil {
86-
http.Error(w, "Failed to read input", http.StatusBadRequest)
87-
return
88-
}
89-
90-
result := solution(input)
91-
if _, err = w.Write([]byte(result)); err != nil {
92-
http.Error(w, "Error", http.StatusInternalServerError)
93-
return
94-
}
95-
}
96-
}
97-
98-
func healthCheckHandler(
99-
w http.ResponseWriter,
100-
r *http.Request,
101-
) {
102-
if err := whitelistMethods([]string{"GET", "POST"}, w, r); err != nil {
103-
fmt.Print(err.Error())
104-
return
105-
}
106-
107-
if _, err := w.Write([]byte("{ \"status\": \"UP\" }")); err != nil {
108-
http.Error(w, "Error", http.StatusInternalServerError)
109-
return
110-
}
111-
}
112-
113-
func unknownPathHandler(
114-
w http.ResponseWriter,
115-
r *http.Request,
116-
) {
117-
msg := fmt.Sprintf("Invalid path: %s", r.URL.Path)
118-
http.Error(w, msg, http.StatusNotFound)
119-
}
120-
121-
func defaultSolutionHandler(
122-
day int,
123-
part int,
124-
) Solution {
125-
return func(input string) string {
126-
return fmt.Sprintf("Solution for day %d part %d not implemented yet", day, part)
127-
}
128-
}
129-
130-
func whitelistMethods(
131-
methods []string,
132-
w http.ResponseWriter,
133-
r *http.Request,
134-
) error {
135-
for _, method := range methods {
136-
if r.Method == method {
137-
return nil
138-
}
139-
}
140-
141-
http.Error(
142-
w,
143-
fmt.Sprintf("%s is not in allowed methods: %q", r.Method, methods),
144-
http.StatusMethodNotAllowed,
145-
)
146-
return fmt.Errorf("expected method to be one of %q, but was %s", methods, r.Method)
147-
}
148-
149-
func readInput(r *http.Request) (string, error) {
150-
body, err := io.ReadAll(r.Body)
151-
if err != nil {
152-
return "", err
153-
}
154-
155-
defer r.Body.Close()
156-
return string(body), nil
157-
}

common/util/solution.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package util
2+
3+
type Solution = func(string) string

common/web/http.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package web
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"github.com/terminalnode/adventofcode2024/common/util"
7+
"log"
8+
"net/http"
9+
"os"
10+
)
11+
12+
func CreateHttpServer(
13+
day int,
14+
part1 util.Solution,
15+
part2 util.Solution,
16+
) *http.Server {
17+
prefix := os.Getenv("AOC2024_PREFIX")
18+
19+
server := &http.Server{Addr: ":8080", Handler: nil}
20+
addHealthCheckHandlers(prefix)
21+
addSolutionHandlers(prefix, day, part1, part2)
22+
addUnknownPathHandlers()
23+
24+
go func() {
25+
log.Printf("Starting Day #%d service on port 8080", day)
26+
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
27+
log.Fatalf("Fatal server error: %v", err)
28+
}
29+
}()
30+
return server
31+
}
32+
33+
func addSolutionHandlers(
34+
prefix string,
35+
day int,
36+
part1 util.Solution,
37+
part2 util.Solution,
38+
) {
39+
http.HandleFunc(addPrefix(prefix, "/1"), createSolutionHandler(day, 1, part1))
40+
http.HandleFunc(addPrefix(prefix, "/2"), createSolutionHandler(day, 2, part2))
41+
}
42+
43+
func addHealthCheckHandlers(prefix string) {
44+
http.HandleFunc(addPrefix(prefix, "/health"), healthCheckHandler)
45+
http.HandleFunc(addPrefix(prefix, "/health/live"), healthCheckHandler)
46+
http.HandleFunc(addPrefix(prefix, "/health/ready"), healthCheckHandler)
47+
48+
if prefix != "" {
49+
// Add non-prefixed handlers as well
50+
http.HandleFunc("/health", healthCheckHandler)
51+
http.HandleFunc("/health/live", healthCheckHandler)
52+
http.HandleFunc("/health/ready", healthCheckHandler)
53+
}
54+
}
55+
56+
func addUnknownPathHandlers() {
57+
http.HandleFunc("/", unknownPathHandler)
58+
}
59+
60+
func healthCheckHandler(
61+
w http.ResponseWriter,
62+
r *http.Request,
63+
) {
64+
if err := whitelistMethods([]string{"GET", "POST"}, w, r); err != nil {
65+
fmt.Print(err.Error())
66+
return
67+
}
68+
69+
if _, err := w.Write([]byte("{ \"status\": \"UP\" }")); err != nil {
70+
http.Error(w, "Error", http.StatusInternalServerError)
71+
return
72+
}
73+
}
74+
75+
func unknownPathHandler(
76+
w http.ResponseWriter,
77+
r *http.Request,
78+
) {
79+
msg := fmt.Sprintf("Invalid path: %s", r.URL.Path)
80+
http.Error(w, msg, http.StatusNotFound)
81+
}
82+
83+
func whitelistMethods(
84+
methods []string,
85+
w http.ResponseWriter,
86+
r *http.Request,
87+
) error {
88+
for _, method := range methods {
89+
if r.Method == method {
90+
return nil
91+
}
92+
}
93+
94+
http.Error(
95+
w,
96+
fmt.Sprintf("%s is not in allowed methods: %q", r.Method, methods),
97+
http.StatusMethodNotAllowed,
98+
)
99+
return fmt.Errorf("expected method to be one of %q, but was %s", methods, r.Method)
100+
}

common/web/http_util.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package web
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
)
8+
9+
func addPrefix(prefix string, url string) string {
10+
if prefix == "" {
11+
return url
12+
}
13+
return fmt.Sprintf("/%s%s", prefix, url)
14+
}
15+
16+
func readInput(r *http.Request) (string, error) {
17+
body, err := io.ReadAll(r.Body)
18+
if err != nil {
19+
return "", err
20+
}
21+
22+
defer r.Body.Close()
23+
return string(body), nil
24+
}

common/web/solution_handler.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package web
2+
3+
import (
4+
"fmt"
5+
"github.com/terminalnode/adventofcode2024/common/util"
6+
"net/http"
7+
)
8+
9+
func createSolutionHandler(
10+
day int,
11+
part int,
12+
solution func(string) string,
13+
) func(http.ResponseWriter, *http.Request) {
14+
if solution == nil {
15+
solution = defaultSolutionHandler(day, part)
16+
}
17+
18+
return func(w http.ResponseWriter, r *http.Request) {
19+
if err := whitelistMethods([]string{"POST"}, w, r); err != nil {
20+
fmt.Print(err.Error())
21+
return
22+
}
23+
24+
input, err := readInput(r)
25+
if err != nil {
26+
http.Error(w, "Failed to read input", http.StatusBadRequest)
27+
return
28+
}
29+
30+
result := solution(input)
31+
if _, err = w.Write([]byte(result)); err != nil {
32+
http.Error(w, "Error", http.StatusInternalServerError)
33+
return
34+
}
35+
}
36+
}
37+
38+
func defaultSolutionHandler(
39+
day int,
40+
part int,
41+
) util.Solution {
42+
return func(input string) string {
43+
return fmt.Sprintf("Solution for day %d part %d not implemented yet", day, part)
44+
}
45+
}

0 commit comments

Comments
 (0)