func (w httpWMetricsLogger) Write(buf []byte) (int, error) {
	n, err := w.ResponseWriter.Write(buf)
	metrics.AddTXBytes(int64(n))
	return n, err
}
Write wraps http.ResponseWriter's Write, and counts the number of bytes transmitted
package core
import (
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"net/http/pprof"
	"path"
	"runtime"
	"strings"
	"time"
	"github.com/mdlayher/wavepipe/api"
	"github.com/mdlayher/wavepipe/api/auth"
	"github.com/mdlayher/wavepipe/config"
	"github.com/mdlayher/wavepipe/data"
	"github.com/mdlayher/wavepipe/env"
	"github.com/mdlayher/wavepipe/metrics"
	"github.com/mdlayher/wavepipe/subsonic"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/phyber/negroni-gzip/gzip"
	"github.com/stretchr/graceful"
	"github.com/unrolled/render"
)
func apiRouter(apiKillChan chan struct{}) {
	log.Println("api: starting...")
	n := negroni.New()
	r := render.New(render.Options{
		IndentJSON: true,
		IndentXML:  true,
	})
	n.Use(gzip.Gzip(gzip.DefaultCompression))
	n.Use(negroni.HandlerFunc(func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
		res.Header().Set("Server", fmt.Sprintf("%s/%s (%s_%s)", App, Version, runtime.GOOS, runtime.GOARCH))
		context.Set(req, api.CtxRender, r)
		req.Body = httpRMetricsLogger{req.Body}
		metricsRes := httpWMetricsLogger{res}
		if env.IsDebug() {
			log.Println(req.Header)
			log.Println(req.URL)
			next(httpWDebugLogger{metricsRes}, req)
			return
		}
		next(metricsRes, req)
		return
	}))
	n.Use(negroni.HandlerFunc(func(res http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
		user, session, clientErr, serverErr := auth.Factory(req.URL.Path).Authenticate(req)
		if clientErr != nil {
			if subErr, ok := clientErr.(*subsonic.Container); ok {
				r.XML(res, 200, subErr)
				return
			}
			if env.IsDebug() && (clientErr == auth.ErrNoUsername || clientErr == auth.ErrNoPassword) {
				res.Header().Set("WWW-Authenticate", "Basic")
			}
			r.JSON(res, 401, api.ErrorResponse{
				Error: &api.Error{
					Code:    401,
					Message: "authentication failed: " + clientErr.Error(),
				},
			})
			return
		}
		if serverErr != nil {
			log.Println(serverErr)
			if subErr, ok := serverErr.(*subsonic.Container); ok {
				r.XML(res, 200, subErr)
				return
			}
			r.JSON(res, 500, api.ErrorResponse{
				Error: &api.Error{
					Code:    500,
					Message: "server error",
				},
			})
			return
		}
		context.Set(req, api.CtxUser, user)
		context.Set(req, api.CtxSession, session)
		log.Printf("api: [%s] %s %s?%s", req.RemoteAddr, req.Method, req.URL.Path, req.URL.Query().Encode())
		next(res, req)
	}))
	gracefulChan := make(chan struct{}, 0)
	n.UseHandler(newRouter())
	go func() {
		conf, err := config.C.Load()
		if err != nil {
			log.Println(err)
			return
		}
		if conf.Host == "" {
			log.Fatalf("api: no host specified in configuration")
		}
		log.Println("api: binding to host", conf.Host)
		if err := graceful.ListenAndServe(&http.Server{Addr: conf.Host, Handler: n}, 10*time.Second); err != nil {
			if strings.Contains(err.Error(), "address already in use") {
				log.Fatalf("api: cannot bind to %s, is wavepipe already running?", conf.Host)
			}
			if !strings.Contains(err.Error(), "use of closed network connection") {
				log.Println(err)
			}
		}
		close(gracefulChan)
	}()
	for {
		select {
		case <-apiKillChan:
			if !env.IsTest() {
				log.Println("api: waiting for remaining connections to close...")
				<-gracefulChan
			}
			log.Println("api: stopped!")
			apiKillChan <- struct{}{}
			return
		}
	}
}
func newRouter() *mux.Router {
	router := mux.NewRouter().StrictSlash(false)
	webUI := func(res http.ResponseWriter, req *http.Request) {
		r := context.Get(req, api.CtxRender).(*render.Render)
		name := mux.Vars(req)["asset"]
		if name == "" {
			name = "index.html"
		}
		if env.IsDebug() {
			log.Println("web: fetching resource: res/web/" + name)
		}
		asset, err := data.Asset("res/web/" + name)
		if err != nil {
			res.WriteHeader(404)
			return
		}
		res.Header().Set("Content-Type", mime.TypeByExtension(path.Ext(name)))
		r.Data(res, 200, asset)
	}
	router.HandleFunc("/", webUI).Methods("GET")
	router.HandleFunc("/res/{asset:.*}", webUI).Methods("GET")
	router.HandleFunc("/robots.txt", func(res http.ResponseWriter, req *http.Request) {
		res.Write([]byte("# wavepipe media server\n" +
			"# https://github.com/mdlayher/wavepipe\n" +
			"User-agent: *\n" +
			"Disallow: /"))
	}).Methods("GET")
	router.HandleFunc("/revision", func(res http.ResponseWriter, req *http.Request) {
		res.Write([]byte(Revision))
	}).Methods("GET")
	router.HandleFunc("/api", api.APIInfo).Methods("GET")
	ar := router.PathPrefix("/api/{version}/").Subrouter()
	ar.HandleFunc("/albums", api.GetAlbums).Methods("GET")
	ar.HandleFunc("/albums/{id}", api.GetAlbums).Methods("GET")
	ar.HandleFunc("/art", api.GetArt).Methods("GET")
	ar.HandleFunc("/art/{id}", api.GetArt).Methods("GET")
	ar.HandleFunc("/artists", api.GetArtists).Methods("GET")
	ar.HandleFunc("/artists/{id}", api.GetArtists).Methods("GET")
	ar.HandleFunc("/folders", api.GetFolders).Methods("GET")
	ar.HandleFunc("/folders/{id}", api.GetFolders).Methods("GET")
	ar.HandleFunc("/lastfm", api.PostLastFM).Methods("POST")
	ar.HandleFunc("/lastfm/{action}", api.PostLastFM).Methods("POST")
	ar.HandleFunc("/lastfm/{action}/{id}", api.PostLastFM).Methods("POST")
	ar.HandleFunc("/login", api.PostLogin).Methods("POST")
	ar.HandleFunc("/logout", api.PostLogout).Methods("POST")
	ar.HandleFunc("/search", api.GetSearch).Methods("GET")
	ar.HandleFunc("/search/{query}", api.GetSearch).Methods("GET")
	ar.HandleFunc("/songs", api.GetSongs).Methods("GET")
	ar.HandleFunc("/songs/{id}", api.GetSongs).Methods("GET")
	ar.HandleFunc("/status", api.GetStatus).Methods("GET")
	ar.HandleFunc("/stream", api.GetStream).Methods("GET")
	ar.HandleFunc("/stream/{id}", api.GetStream).Methods("GET")
	ar.HandleFunc("/transcode", api.GetTranscode).Methods("GET")
	ar.HandleFunc("/transcode/{id}", api.GetTranscode).Methods("GET")
	ar.HandleFunc("/users", api.GetUsers).Methods("GET")
	ar.HandleFunc("/users/{id}", api.GetUsers).Methods("GET")
	ar.HandleFunc("/users", api.PostUsers).Methods("POST")
	ar.HandleFunc("/users/{id}", api.PutUsers).Methods("PUT", "PATCH")
	ar.HandleFunc("/users/{id}", api.DeleteUsers).Methods("DELETE")
	ar.HandleFunc("/waveform", api.GetWaveform).Methods("GET")
	ar.HandleFunc("/waveform/{id}", api.GetWaveform).Methods("GET")
	sr := router.PathPrefix("/subsonic/rest").Subrouter()
	sr.HandleFunc("/ping.view", subsonic.Ping)
	sr.HandleFunc("/getAlbumList2.view", subsonic.GetAlbumList2)
	sr.HandleFunc("/getAlbum.view", subsonic.GetAlbum)
	sr.HandleFunc("/getCoverArt.view", subsonic.GetCoverArt)
	sr.HandleFunc("/getIndexes.view", subsonic.GetIndexes)
	sr.HandleFunc("/getLicense.view", subsonic.GetLicense)
	sr.HandleFunc("/getMusicDirectory.view", subsonic.GetMusicDirectory)
	sr.HandleFunc("/getMusicFolders.view", subsonic.GetMusicFolders)
	sr.HandleFunc("/getPlaylists.view", subsonic.GetPlaylists)
	sr.HandleFunc("/getRandomSongs.view", subsonic.GetRandomSongs)
	sr.HandleFunc("/getStarred.view", subsonic.GetStarred)
	sr.HandleFunc("/stream.view", subsonic.Stream)
	if env.IsDebug() {
		dr := router.PathPrefix("/debug/pprof").Subrouter()
		dr.HandleFunc("/", pprof.Index)
		dr.HandleFunc("/cmdline", pprof.Cmdline)
		dr.HandleFunc("/profile", pprof.Profile)
		dr.HandleFunc("/symbol", pprof.Symbol)
		dr.HandleFunc("/block", pprof.Handler("block").ServeHTTP)
		dr.HandleFunc("/heap", pprof.Handler("heap").ServeHTTP)
		dr.HandleFunc("/goroutine", pprof.Handler("goroutine").ServeHTTP)
		dr.HandleFunc("/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
	}
	return router
}
type httpWDebugLogger struct {
	http.ResponseWriter
}
func (w httpWDebugLogger) Write(buf []byte) (int, error) {
	contentType := w.ResponseWriter.Header().Get("Content-Type")
	if strings.Contains(contentType, render.ContentJSON) || strings.Contains(contentType, render.ContentXML) {
		log.Println(string(buf))
	}
	return w.ResponseWriter.Write(buf)
}
type httpRMetricsLogger struct {
	io.ReadCloser
}
func (r httpRMetricsLogger) Read(buf []byte) (int, error) {
	n, err := r.ReadCloser.Read(buf)
	metrics.AddRXBytes(int64(n))
	return n, err
}
type httpWMetricsLogger struct {
	http.ResponseWriter
}