| // 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" |
| "io/ioutil" |
| "log" |
| "net/http" |
| "os" |
| "path/filepath" |
| "strings" |
| "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) |
| 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() |
| |
| static := *staticFlag |
| bin := filepath.SplitList(*binFlag) |
| fidl := filepath.SplitList(*fidlFlag) |
| if static == "" || len(bin) == 0 || len(fidl) == 0 { |
| fuchsia := getFuchsiaDir() |
| if static == "" { |
| static = filepath.Join(filepath.Dir(fuchsia), "fidlbolt", "frontend", "dist") |
| } |
| if len(bin) == 0 { |
| build := getBuildDir(fuchsia) |
| bin = []string{ |
| filepath.Join(build, "host_x64"), |
| filepath.Join(build+".zircon", "tools"), |
| } |
| } |
| if len(fidl) == 0 { |
| fidl = []string{ |
| filepath.Join(fuchsia, "sdk", "fidl"), |
| filepath.Join(fuchsia, "zircon", "system", "fidl"), |
| } |
| } |
| } |
| checkDirs(static) |
| checkDirs(bin...) |
| checkDirs(fidl...) |
| |
| server, err := NewServer(bin, 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.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 getFuchsiaDir() string { |
| dir, ok := os.LookupEnv("FUCHSIA_DIR") |
| if !ok { |
| log.Fatal("Must set FUCHSIA_DIR or provide all flags") |
| } |
| return dir |
| } |
| |
| func getBuildDir(fuchsia string) string { |
| b, err := ioutil.ReadFile(filepath.Join(fuchsia, ".fx-build-dir")) |
| if err != nil { |
| log.Fatal(err) |
| } |
| return filepath.Join(fuchsia, strings.TrimSpace(string(b))) |
| } |
| |
| 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()) |
| } |
| } |