From 4b055bf260768780fce345fdb88abeb826122315 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Tue, 20 Dec 2022 16:28:36 +0900 Subject: [PATCH] Rewrite HTTP server without net/http This cuts down the binary size from 5.7MB to 3.3MB. --- src/server.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++++ src/terminal.go | 37 +--------------- 2 files changed, 113 insertions(+), 36 deletions(-) create mode 100644 src/server.go diff --git a/src/server.go b/src/server.go new file mode 100644 index 0000000..f196ede --- /dev/null +++ b/src/server.go @@ -0,0 +1,112 @@ +package fzf + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "net" + "strconv" + "strings" +) + +const ( + crlf = "\r\n" + httpOk = "HTTP/1.1 200 OK" + crlf + httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf +) + +func startHttpServer(port int, channel chan []*action) { + if port == 0 { + return + } + + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + return + } + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + if errors.Is(err, net.ErrClosed) { + break + } else { + continue + } + } + conn.Write([]byte(handleHttpRequest(conn, channel))) + conn.Close() + } + listener.Close() + }() +} + +// Here we are writing a simplistic HTTP server without using net/http +// package to reduce the size of the binary. +// +// * No --listen: 2.8MB +// * --listen with net/http: 5.7MB +// * --listen w/o net/http: 3.3MB +func handleHttpRequest(conn net.Conn, channel chan []*action) string { + line := 0 + headerRead := false + contentLength := 0 + body := "" + bad := func(message string) string { + message += "\n" + return httpBadRequest + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message) + } + scanner := bufio.NewScanner(conn) + scanner.Split(func(data []byte, atEOF bool) (int, []byte, error) { + found := bytes.Index(data, []byte(crlf)) + if found >= 0 { + token := data[:found+len(crlf)] + return len(token), token, nil + } + if atEOF || len(body)+len(data) >= contentLength { + return 0, data, bufio.ErrFinalToken + } + return 0, nil, nil + }) + + for scanner.Scan() { + text := scanner.Text() + if line == 0 && !strings.HasPrefix(text, "POST / HTTP") { + return bad("invalid request method") + } + if text == crlf { + headerRead = true + } + if !headerRead { + pair := strings.SplitN(text, ":", 2) + if len(pair) == 2 && strings.ToLower(pair[0]) == "content-length" { + length, err := strconv.Atoi(strings.TrimSpace(pair[1])) + if err != nil { + return bad("invalid content length") + } + contentLength = length + } + } else if contentLength <= 0 { + break + } else { + body += text + } + line++ + } + + errorMessage := "" + actions := parseSingleActionList(strings.TrimSpace(string(body)), func(message string) { + errorMessage = message + }) + if len(errorMessage) > 0 { + return bad(errorMessage) + } + if len(actions) == 0 { + return bad("no action specified") + } + + channel <- actions + return httpOk +} diff --git a/src/terminal.go b/src/terminal.go index 47cc34c..0d99637 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -3,10 +3,8 @@ package fzf import ( "bufio" "fmt" - "io" "io/ioutil" "math" - "net/http" "os" "os/signal" "regexp" @@ -626,39 +624,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { return &t } -func (t *Terminal) startServer() { - if t.listenPort == 0 { - return - } - - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - w.WriteHeader(http.StatusBadRequest) - return - } - body, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - response := "" - actions := parseSingleActionList(string(body), func(message string) { - response = message - }) - - if len(response) > 0 { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, response) - return - } - t.serverChan <- actions - }) - go func() { - http.ListenAndServe(fmt.Sprintf(":%d", t.listenPort), nil) - }() -} - func borderLines(shape tui.BorderShape) int { switch shape { case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble: @@ -2535,7 +2500,7 @@ func (t *Terminal) Loop() { looping := true _, startEvent := t.keymap[tui.Start.AsEvent()] - t.startServer() + startHttpServer(t.listenPort, t.serverChan) eventChan := make(chan tui.Event) needBarrier := true barrier := make(chan bool)