summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2022-12-20 16:28:36 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2022-12-21 01:35:08 +0900
commit4b055bf260768780fce345fdb88abeb826122315 (patch)
treedd10e35ee5a27668e0087642f39b8bbd473fd874 /src
parent1ba7484d606bf3797b3936651051bb4113dbcad2 (diff)
downloadfzf-4b055bf260768780fce345fdb88abeb826122315.tar.gz
Rewrite HTTP server without net/http
This cuts down the binary size from 5.7MB to 3.3MB.
Diffstat (limited to 'src')
-rw-r--r--src/server.go112
-rw-r--r--src/terminal.go37
2 files changed, 113 insertions, 36 deletions
diff --git a/src/server.go b/src/server.go
new file mode 100644
index 00000000..f196edee
--- /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 47cc34cf..0d99637c 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)