diff --git a/Makefile b/Makefile index d0b8b88..5a29ece 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,6 @@ BUILDTIME = $(shell date +%Y.%m.%d_%H%M) all: build ./forged merge - ./forged build: goimports GO111MODULE=off go build \ @@ -34,10 +33,9 @@ start: systemctl start forged.service stop: - systemctl stop forged.service + -systemctl stop forged.service -run: build - systemctl stop forged.service +run: build stop ./forged --daemon # setcap 'cap_net_bind_service=+ep' forged # allow the binary to open ports below 1024 # diff --git a/handlePatches.go b/handlePatches.go index c9d7d38..3a0317c 100644 --- a/handlePatches.go +++ b/handlePatches.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "net/http" "strings" @@ -9,8 +8,8 @@ import ( "go.wit.com/log" ) -func handlePatches(w http.ResponseWriter, r *http.Request) error { - pb, err := marshalPatchesPB(r) +func handlePatches(w http.ResponseWriter, r *http.Request, data []byte) error { + pb, err := marshalPatchesPB(r, data) if err != nil { return sendPatchesError(w, pb, err) } @@ -22,9 +21,10 @@ func handlePatches(w http.ResponseWriter, r *http.Request) error { if route == "" { route = "route was blank" } + log.Info("GOT PATCHES ROUTE", route, "with # patches =", pb.Len()) if strings.HasPrefix(route, "/patches/old") { - processPatchesPB(r, pb) - } else if strings.HasPrefix(route, "/patches/old") { + processPatchesOldPB(r, pb) + } else if strings.HasPrefix(route, "/patches/new") { log.Info("add new patches") } else { log.Info("unknown route", route) @@ -38,18 +38,14 @@ func sendPatchesError(w http.ResponseWriter, r *forgepb.Patches, err error) erro return nil } -func processPatchesPB(r *http.Request, pb *forgepb.Patches) error { - log.Info("send error back to user") +func processPatchesOldPB(r *http.Request, pb *forgepb.Patches) error { + log.Info("check out these patches") + pb.PrintTable() return nil } -func marshalPatchesPB(r *http.Request) (*forgepb.Patches, error) { +func marshalPatchesPB(r *http.Request, msg []byte) (*forgepb.Patches, error) { pb := forgepb.NewPatches() - msg, err := ioutil.ReadAll(r.Body) // Read the body as []byte - defer r.Body.Close() - if err != nil { - return pb, err - } if err := pb.Unmarshal(msg); err != nil { log.Info("proto.Unmarshal() failed on wire message len", len(msg), err) diff --git a/http.go b/http.go index 4d9a5d6..5d844e1 100644 --- a/http.go +++ b/http.go @@ -49,11 +49,10 @@ func whoSent(r *http.Request) string { } func okHandler(w http.ResponseWriter, r *http.Request) { + // something appears to be buggy. always get this first I guess msg, err := ioutil.ReadAll(r.Body) // Read the body as []byte - if err != nil { - log.Info("ioutil.ReadAll() error =", err) - return - } + r.Body.Close() + log.Info("TRYING TO MARSHAL bytes:", len(msg), err) who := whoSent(r) @@ -76,7 +75,12 @@ func okHandler(w http.ResponseWriter, r *http.Request) { return } - log.Warn("forged REQUEST URL =", requrl, "msg =", len(msg), "from =", who) + log.Warn("forged REQUEST URL =", requrl, "from =", who) + + if strings.HasPrefix(route, "/patches/") { + handlePatches(w, r, msg) + return + } if route == "/patchset" { if err := savePatchset(w, msg); err != nil { @@ -119,15 +123,9 @@ func okHandler(w http.ResponseWriter, r *http.Request) { } if route == "/GetPatchsets" { - data, err := me.forge.Patchsets.Marshal() - if err != nil { - log.Info("patchsets.Marshal() to wire failed", err) - return - } - start := time.Now() - log.Info("going to w.Write(data) with len", len(data)) - w.Write(data) + log.Info("going to w.Write(msg) with len", len(msg)) + w.Write(msg) age := shell.FormatDuration(time.Since(start)) log.Printf("Done with xfer in (%s). happy hacking!\n", age) return @@ -139,11 +137,6 @@ func okHandler(w http.ResponseWriter, r *http.Request) { return } - if strings.HasPrefix(route, "/patches/") { - handlePatches(w, r) - return - } - if route == "/goReference.svg" { w.Header().Set("Content-Type", "image/svg+xml") writeFile(w, "goReference.svg") diff --git a/main.go b/main.go index eb2f68f..62b4241 100644 --- a/main.go +++ b/main.go @@ -76,21 +76,78 @@ func main() { } if argv.Daemon == true { - log.Info("Running in --daemon mode") - http.HandleFunc("/", okHandler) - // go https() // use caddy instead + mux := http.NewServeMux() + okHandlerFunc := http.HandlerFunc(okHandler) + + // Set a limit of 50 kilobytes for requests to this handler. + // Adjust this value to your needs. + const maxUploadSize = 1025 * 1024 // 1 MB + mux.Handle("/", http.MaxBytesHandler(okHandlerFunc, maxUploadSize)) + p := fmt.Sprintf(":%d", argv.Port) - log.Println(argv.Version(), "HOSTNAME set to:", HOSTNAME) - log.Println("Running on port", "http://localhost"+p) - log.Println("Running on port", "http://localhost"+p+"/ipv6.png") - err := http.ListenAndServe(p, nil) - if err != nil { - log.Println("Error starting server:", err) + log.Printf("Server starting on port %s...\n", p) + log.Printf("Test with: curl -d 'hello world' http://localhost:%s/\n", p) + + server := &http.Server{ + Addr: p, + Handler: mux, + ReadTimeout: 5 * time.Minute, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, } - } else { - log.Info("--daemon was not set. Just list the patches.") - doList() + + log.Printf("Server starting on port %s with a 1 MB request body limit...\n", p) + if err := server.ListenAndServe(); err != nil { + log.Fatal("Could not start server: %s\n", err) + } + okExit("") } + + /* + // --- Best Practice: Create a custom http.Server --- + server := &http.Server{ + Addr: p, + Handler: mux, + + // ReadTimeout is the total time to read the entire request, including the body. + // Increase this to a value that can accommodate your largest expected uploads. + // For example, 5 minutes. + ReadTimeout: 5 * time.Minute, + + // WriteTimeout is the maximum duration before timing out writes of the response. + WriteTimeout: 10 * time.Second, + + // IdleTimeout is the maximum amount of time to wait for the + // next request when keep-alives are enabled. + IdleTimeout: 120 * time.Second, + } + */ + + /* + log.Println(argv.Version(), "HOSTNAME set to:", HOSTNAME) + log.Println("Running on port", "http://localhost"+p) + log.Println("Running on port", "http://localhost"+p+"/ipv6.png") + // if err := http.ListenAndServe(p, nil); err != nil { + if err := server.ListenAndServe(); err != nil { + log.Fatalf("Could not start server: %s\n", err) + } + /* + log.Info("Running in --daemon mode") + http.HandleFunc("/", okHandler) + // go https() // use caddy instead + p := fmt.Sprintf(":%d", argv.Port) + log.Println(argv.Version(), "HOSTNAME set to:", HOSTNAME) + log.Println("Running on port", "http://localhost"+p) + log.Println("Running on port", "http://localhost"+p+"/ipv6.png") + err := http.ListenAndServe(p, nil) + if err != nil { + log.Println("Error starting server:", err) + } + return + } + */ + log.Info("--daemon was not set. Just list the patches.") + // doList() } func formatDuration(d time.Duration) string { diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..803e162 --- /dev/null +++ b/middleware.go @@ -0,0 +1,77 @@ +package main + +import ( + "bytes" + "context" + "io/ioutil" + "log" + "net/http" +) + +// Define a key type to avoid context key collisions. +type contextKey string + +const bufferedBodyKey = contextKey("bufferedBody") + +// bufferBodyMiddleware reads the request body and replaces it with a new reader, +// allowing it to be read multiple times. The original body is stored in the request context. +func bufferBodyMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Only buffer if there's a body to read. + if r.Body == nil || r.ContentLength == 0 { + next.ServeHTTP(w, r) + return + } + + bodyBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Printf("Error reading body in middleware: %v\n", err) + return + } + defer r.Body.Close() + + // Store the buffered body in the context for downstream handlers. + ctx := context.WithValue(r.Context(), bufferedBodyKey, bodyBytes) + + // Replace the original body with a new reader on the buffered bytes. + // This allows subsequent handlers to read the body again. + r.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) + + // Call the next handler in the chain with the modified request. + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +/* +// okHandler is the final handler. It can now safely access the body from the context, +// knowing that other middleware might have also read it. +func okHandler(w http.ResponseWriter, r *http.Request) { + // For demonstration, we can try reading the body directly here too. + // The middleware ensures this is a fresh stream of the buffered data. + bodyFromStream, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Printf("Error reading body in handler: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + log.Printf("Handler read %d bytes from the request body stream.", len(bodyFromStream)) + + // We can also retrieve the body from the context if needed. + bodyFromContext, ok := r.Context().Value(bufferedBodyKey).([]byte) + if !ok { + log.Println("Could not retrieve buffered body from context.") + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + log.Printf("Handler retrieved %d bytes from context.", len(bodyFromContext)) + + // Prove they are the same. + if !bytes.Equal(bodyFromStream, bodyFromContext) { + log.Println("FATAL: Body from stream and context do not match!") + } + + fmt.Fprintf(w, "Successfully read body of %d bytes.\n", len(bodyFromContext)) +} +*/