Skip to content

Commit 13e2319

Browse files
committed
git-bundle-web-server: extract server setup into 'bundleWebServer' struct
Move the server setup & execution logic out of 'main.go' and into a new 'bundleWebServer' struct in 'bundle.go'. This brings the web server more in line with 'git-bundle-server', which similarly uses its command structs to contain operational logic. This new design makes passing around the 'context' structure a bit more straightforward, and (eventually) helps avoid needing to pass a logger instance to each function called by 'git-bundle-web-server'. Signed-off-by: Victoria Dye <[email protected]>
1 parent e8713a4 commit 13e2319

File tree

2 files changed

+145
-113
lines changed

2 files changed

+145
-113
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
"os"
9+
"os/signal"
10+
"strings"
11+
"sync"
12+
"syscall"
13+
14+
"github.com/github/git-bundle-server/internal/common"
15+
"github.com/github/git-bundle-server/internal/core"
16+
)
17+
18+
type bundleWebServer struct {
19+
server *http.Server
20+
serverWaitGroup *sync.WaitGroup
21+
listenAndServeFunc func() error
22+
}
23+
24+
func NewBundleWebServer(port string, certFile string, keyFile string) *bundleWebServer {
25+
bundleServer := &bundleWebServer{
26+
serverWaitGroup: &sync.WaitGroup{},
27+
}
28+
29+
// Configure the http.Server
30+
mux := http.NewServeMux()
31+
mux.HandleFunc("/", bundleServer.serve)
32+
bundleServer.server = &http.Server{
33+
Handler: mux,
34+
Addr: ":" + port,
35+
}
36+
37+
if certFile != "" {
38+
bundleServer.listenAndServeFunc = func() error { return bundleServer.server.ListenAndServeTLS(certFile, keyFile) }
39+
} else {
40+
bundleServer.listenAndServeFunc = func() error { return bundleServer.server.ListenAndServe() }
41+
}
42+
43+
return bundleServer
44+
}
45+
46+
func (b *bundleWebServer) parseRoute(ctx context.Context, path string) (string, string, string, error) {
47+
elements := strings.FieldsFunc(path, func(char rune) bool { return char == '/' })
48+
switch len(elements) {
49+
case 0:
50+
return "", "", "", fmt.Errorf("empty route")
51+
case 1:
52+
return "", "", "", fmt.Errorf("route has owner, but no repo")
53+
case 2:
54+
return elements[0], elements[1], "", nil
55+
case 3:
56+
return elements[0], elements[1], elements[2], nil
57+
default:
58+
return "", "", "", fmt.Errorf("path has depth exceeding three")
59+
}
60+
}
61+
62+
func (b *bundleWebServer) serve(w http.ResponseWriter, r *http.Request) {
63+
ctx := r.Context()
64+
65+
user, err := common.NewUserProvider().CurrentUser()
66+
if err != nil {
67+
return
68+
}
69+
fs := common.NewFileSystem()
70+
path := r.URL.Path
71+
72+
owner, repo, file, err := b.parseRoute(ctx, path)
73+
if err != nil {
74+
w.WriteHeader(http.StatusNotFound)
75+
fmt.Printf("Failed to parse route: %s\n", err)
76+
return
77+
}
78+
79+
route := owner + "/" + repo
80+
81+
repos, err := core.GetRepositories(user, fs)
82+
if err != nil {
83+
w.WriteHeader(http.StatusInternalServerError)
84+
fmt.Printf("Failed to load routes\n")
85+
return
86+
}
87+
88+
repository, contains := repos[route]
89+
if !contains {
90+
w.WriteHeader(http.StatusNotFound)
91+
fmt.Printf("Failed to get route out of repos\n")
92+
return
93+
}
94+
95+
if file == "" {
96+
file = "bundle-list"
97+
}
98+
99+
fileToServe := repository.WebDir + "/" + file
100+
data, err := os.ReadFile(fileToServe)
101+
if err != nil {
102+
w.WriteHeader(http.StatusNotFound)
103+
fmt.Printf("Failed to read file\n")
104+
return
105+
}
106+
107+
fmt.Printf("Successfully serving content for %s/%s\n", route, file)
108+
w.Write(data)
109+
}
110+
111+
func (b *bundleWebServer) StartServerAsync(ctx context.Context) {
112+
// Add to wait group
113+
b.serverWaitGroup.Add(1)
114+
115+
go func(ctx context.Context) {
116+
defer b.serverWaitGroup.Done()
117+
118+
// Return error unless it indicates graceful shutdown
119+
err := b.listenAndServeFunc()
120+
if err != nil && err != http.ErrServerClosed {
121+
log.Fatal(err)
122+
}
123+
}(ctx)
124+
125+
fmt.Println("Server is running at address " + b.server.Addr)
126+
}
127+
128+
func (b *bundleWebServer) HandleSignalsAsync(ctx context.Context) {
129+
// Intercept interrupt signals
130+
c := make(chan os.Signal, 1)
131+
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
132+
go func(ctx context.Context) {
133+
<-c
134+
fmt.Println("Starting graceful server shutdown...")
135+
b.server.Shutdown(ctx)
136+
}(ctx)
137+
}
138+
139+
func (b *bundleWebServer) Wait() {
140+
b.serverWaitGroup.Wait()
141+
}

cmd/git-bundle-web-server/main.go

Lines changed: 4 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4,109 +4,12 @@ import (
44
"context"
55
"flag"
66
"fmt"
7-
"log"
8-
"net/http"
97
"os"
10-
"os/signal"
11-
"strings"
12-
"sync"
13-
"syscall"
148

159
"github.com/github/git-bundle-server/cmd/utils"
1610
"github.com/github/git-bundle-server/internal/argparse"
17-
"github.com/github/git-bundle-server/internal/common"
18-
"github.com/github/git-bundle-server/internal/core"
1911
)
2012

21-
func parseRoute(path string) (string, string, string, error) {
22-
elements := strings.FieldsFunc(path, func(char rune) bool { return char == '/' })
23-
switch len(elements) {
24-
case 0:
25-
return "", "", "", fmt.Errorf("empty route")
26-
case 1:
27-
return "", "", "", fmt.Errorf("route has owner, but no repo")
28-
case 2:
29-
return elements[0], elements[1], "", nil
30-
case 3:
31-
return elements[0], elements[1], elements[2], nil
32-
default:
33-
return "", "", "", fmt.Errorf("path has depth exceeding three")
34-
}
35-
}
36-
37-
func serve(w http.ResponseWriter, r *http.Request) {
38-
user, err := common.NewUserProvider().CurrentUser()
39-
if err != nil {
40-
return
41-
}
42-
fs := common.NewFileSystem()
43-
path := r.URL.Path
44-
45-
owner, repo, file, err := parseRoute(path)
46-
if err != nil {
47-
w.WriteHeader(http.StatusNotFound)
48-
fmt.Printf("Failed to parse route: %s\n", err)
49-
return
50-
}
51-
52-
route := owner + "/" + repo
53-
54-
repos, err := core.GetRepositories(user, fs)
55-
if err != nil {
56-
w.WriteHeader(http.StatusInternalServerError)
57-
fmt.Printf("Failed to load routes\n")
58-
return
59-
}
60-
61-
repository, contains := repos[route]
62-
if !contains {
63-
w.WriteHeader(http.StatusNotFound)
64-
fmt.Printf("Failed to get route out of repos\n")
65-
return
66-
}
67-
68-
if file == "" {
69-
file = "bundle-list"
70-
}
71-
72-
fileToServe := repository.WebDir + "/" + file
73-
data, err := os.ReadFile(fileToServe)
74-
if err != nil {
75-
w.WriteHeader(http.StatusNotFound)
76-
fmt.Printf("Failed to read file\n")
77-
return
78-
}
79-
80-
fmt.Printf("Successfully serving content for %s/%s\n", route, file)
81-
w.Write(data)
82-
}
83-
84-
func startServer(server *http.Server,
85-
cert string, key string,
86-
serverWaitGroup *sync.WaitGroup,
87-
) {
88-
// Add to wait group
89-
serverWaitGroup.Add(1)
90-
91-
go func() {
92-
defer serverWaitGroup.Done()
93-
94-
// Return error unless it indicates graceful shutdown
95-
var err error
96-
if cert != "" {
97-
err = server.ListenAndServeTLS(cert, key)
98-
} else {
99-
err = server.ListenAndServe()
100-
}
101-
102-
if err != nil && err != http.ErrServerClosed {
103-
log.Fatal(err)
104-
}
105-
}()
106-
107-
fmt.Println("Server is running at address " + server.Addr)
108-
}
109-
11013
func main() {
11114
ctx := context.Background()
11215

@@ -124,28 +27,16 @@ func main() {
12427
key := utils.GetFlagValue[string](parser, "key")
12528

12629
// Configure the server
127-
mux := http.NewServeMux()
128-
mux.HandleFunc("/", serve)
129-
server := &http.Server{
130-
Handler: mux,
131-
Addr: ":" + port,
132-
}
133-
serverWaitGroup := &sync.WaitGroup{}
30+
bundleServer := NewBundleWebServer(port, cert, key)
13431

13532
// Start the server asynchronously
136-
startServer(server, cert, key, serverWaitGroup)
33+
bundleServer.StartServerAsync(ctx)
13734

13835
// Intercept interrupt signals
139-
c := make(chan os.Signal, 1)
140-
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
141-
go func() {
142-
<-c
143-
fmt.Println("Starting graceful server shutdown...")
144-
server.Shutdown(ctx)
145-
}()
36+
bundleServer.HandleSignalsAsync(ctx)
14637

14738
// Wait for server to shut down
148-
serverWaitGroup.Wait()
39+
bundleServer.Wait()
14940

15041
fmt.Println("Shutdown complete")
15142
}

0 commit comments

Comments
 (0)