blob: 647d550d880af51b4cc35534a53040040a73240b [file] [log] [blame]
// 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())
}
}