diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2022-12-20 16:28:36 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2022-12-21 01:35:08 +0900 |
commit | 4b055bf260768780fce345fdb88abeb826122315 (patch) | |
tree | dd10e35ee5a27668e0087642f39b8bbd473fd874 /src/server.go | |
parent | 1ba7484d606bf3797b3936651051bb4113dbcad2 (diff) |
Rewrite HTTP server without net/http
This cuts down the binary size from 5.7MB to 3.3MB.
Diffstat (limited to 'src/server.go')
-rw-r--r-- | src/server.go | 112 |
1 files changed, 112 insertions, 0 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 +} |