blob: 76d79d3335f4839eb62bd95a7017317612110e8a [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"
"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())
}
}