| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "encoding/json" |
| "flag" |
| "fmt" |
| "log" |
| "net/http" |
| "os" |
| "path/filepath" |
| "time" |
| ) |
| |
| var ( |
| portFlag = flag.String("port", "", "port to serve on") |
| verboseFlag = flag.Bool("verbose", false, "enable verbose logging") |
| staticFlag = flag.String("static", "", "static files directory") |
| binFlag = flag.String("bin", "", "binary search paths, "+pathHelp) |
| etcFlag = flag.String("etc", "", "config file search paths, "+pathHelp) |
| fidlFlag = flag.String("fidl", "", "FIDL library search paths, "+pathHelp) |
| |
| pathHelp = fmt.Sprintf(`separated by "%s"`, string(filepath.ListSeparator)) |
| ) |
| |
| func printUsage() { |
| program := filepath.Base(os.Args[0]) |
| message := |
| `Usage: ` + program + ` [flags] |
| |
| Web server for fidlbolt, a tool for exploring FIDL code and bytes |
| |
| Flags: |
| ` |
| |
| fmt.Fprint(flag.CommandLine.Output(), message) |
| flag.PrintDefaults() |
| } |
| |
| func main() { |
| flag.Usage = printUsage |
| flag.Parse() |
| |
| // Remove timestamps from logs for startup error messages. |
| log.SetFlags(0) |
| |
| static := *staticFlag |
| bin := filepath.SplitList(*binFlag) |
| etc := filepath.SplitList(*etcFlag) |
| fidl := filepath.SplitList(*fidlFlag) |
| if static == "" || len(bin) == 0 || len(etc) == 0 || len(fidl) == 0 { |
| log.Print("must provide -static, -bin, -etc, and -fidl") |
| printUsage() |
| os.Exit(1) |
| } |
| checkDirs(static) |
| checkDirs(bin...) |
| checkDirs(etc...) |
| checkDirs(fidl...) |
| |
| server, err := newServer(bin, etc, fidl) |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| mux := http.NewServeMux() |
| mux.Handle("/", http.FileServer(http.Dir(static))) |
| mux.Handle("/convert", &postHandler{server, *verboseFlag}) |
| |
| timeout := 10 * time.Second |
| handler := http.TimeoutHandler( |
| mux, timeout, fmt.Sprintf("Request exceeded the %v time limit", timeout)) |
| handler = logging(handler) |
| |
| port := *portFlag |
| if port == "" { |
| port = os.Getenv("PORT") |
| if port == "" { |
| port = "8080" |
| } |
| } |
| |
| s := &http.Server{ |
| Addr: ":" + port, |
| Handler: handler, |
| ReadTimeout: 10 * time.Second, |
| WriteTimeout: timeout + 5*time.Second, |
| } |
| log.Printf("Listening on %s", s.Addr) |
| log.SetFlags(log.LstdFlags) |
| log.Fatal(s.ListenAndServe()) |
| } |
| |
| type postHandler struct { |
| server *server |
| verbose bool |
| } |
| |
| func (h *postHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| if r.Method != http.MethodPost { |
| writeError(w, http.StatusMethodNotAllowed, nil) |
| return |
| } |
| if r.Body == nil { |
| writeError(w, http.StatusBadRequest, nil) |
| return |
| } |
| var request request |
| if err := json.NewDecoder(r.Body).Decode(&request); err != nil { |
| writeError(w, http.StatusBadRequest, err) |
| return |
| } |
| if h.verbose { |
| log.Printf("Request body: %#v", request) |
| } |
| if err := request.validate(); err != nil { |
| writeError(w, http.StatusUnprocessableEntity, err) |
| return |
| } |
| response, err := h.server.serve(r.Context(), &request) |
| if err != nil { |
| writeError(w, http.StatusInternalServerError, err) |
| return |
| } |
| body, err := json.Marshal(response) |
| if err != nil { |
| writeError(w, http.StatusInternalServerError, err) |
| return |
| } |
| w.Header().Set("Content-Type", "application/json") |
| w.WriteHeader(http.StatusOK) |
| w.Write(body) |
| } |
| |
| func checkDirs(dirs ...string) { |
| for _, dir := range dirs { |
| if _, err := os.Stat(dir); os.IsNotExist(err) { |
| log.Fatalf("%s: directory does not exist", dir) |
| } |
| } |
| } |
| |
| type statusWriter struct { |
| http.ResponseWriter |
| status int |
| } |
| |
| func (sw *statusWriter) WriteHeader(status int) { |
| sw.status = status |
| sw.ResponseWriter.WriteHeader(status) |
| } |
| |
| func logging(handler http.Handler) http.Handler { |
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.URL) |
| sw := statusWriter{ResponseWriter: w} |
| handler.ServeHTTP(&sw, r) |
| log.Printf("[%s] -> %d %s", r.RemoteAddr, sw.status, http.StatusText(sw.status)) |
| }) |
| } |
| |
| func writeError(w http.ResponseWriter, code int, err error) { |
| text := fmt.Sprintf("%d %s", code, http.StatusText(code)) |
| http.Error(w, text, code) |
| if err != nil { |
| log.Printf("ERROR: %s", err.Error()) |
| } |
| } |