| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2017-02-15 21:29:20 +08:00
										 |  |  | 	"crypto/tls" | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2017-04-27 14:54:21 +08:00
										 |  |  | 	"net" | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	macaron "gopkg.in/macaron.v1" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/api/live" | 
					
						
							|  |  |  | 	httpstatic "github.com/grafana/grafana/pkg/api/static" | 
					
						
							| 
									
										
										
										
											2017-04-25 23:17:45 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/bus" | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" | 
					
						
							| 
									
										
										
										
											2017-04-25 23:17:45 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/components/simplejson" | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/log" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/middleware" | 
					
						
							| 
									
										
										
										
											2017-04-25 23:17:45 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type HttpServer struct { | 
					
						
							|  |  |  | 	log           log.Logger | 
					
						
							|  |  |  | 	macaron       *macaron.Macaron | 
					
						
							|  |  |  | 	context       context.Context | 
					
						
							|  |  |  | 	streamManager *live.StreamManager | 
					
						
							| 
									
										
										
										
											2017-02-06 16:40:07 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	httpSrv *http.Server | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func NewHttpServer() *HttpServer { | 
					
						
							|  |  |  | 	return &HttpServer{ | 
					
						
							|  |  |  | 		log: log.New("http.server"), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (hs *HttpServer) Start(ctx context.Context) error { | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hs.context = ctx | 
					
						
							|  |  |  | 	hs.streamManager = live.NewStreamManager() | 
					
						
							|  |  |  | 	hs.macaron = hs.newMacaron() | 
					
						
							|  |  |  | 	hs.registerRoutes() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hs.streamManager.Run(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort) | 
					
						
							| 
									
										
										
										
											2017-04-27 14:54:21 +08:00
										 |  |  | 	hs.log.Info("Initializing HTTP Server", "address", listenAddr, "protocol", setting.Protocol, "subUrl", setting.AppSubUrl, "socket", setting.SocketPath) | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-06 16:40:07 +08:00
										 |  |  | 	hs.httpSrv = &http.Server{Addr: listenAddr, Handler: hs.macaron} | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	switch setting.Protocol { | 
					
						
							|  |  |  | 	case setting.HTTP: | 
					
						
							| 
									
										
										
										
											2017-02-06 16:40:07 +08:00
										 |  |  | 		err = hs.httpSrv.ListenAndServe() | 
					
						
							|  |  |  | 		if err == http.ErrServerClosed { | 
					
						
							|  |  |  | 			hs.log.Debug("server was shutdown gracefully") | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	case setting.HTTPS: | 
					
						
							| 
									
										
										
										
											2017-02-06 16:40:07 +08:00
										 |  |  | 		err = hs.httpSrv.ListenAndServeTLS(setting.CertFile, setting.KeyFile) | 
					
						
							|  |  |  | 		if err == http.ErrServerClosed { | 
					
						
							|  |  |  | 			hs.log.Debug("server was shutdown gracefully") | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-04-27 14:54:21 +08:00
										 |  |  | 	case setting.SOCKET: | 
					
						
							|  |  |  | 		ln, err := net.Listen("unix", setting.SocketPath) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			hs.log.Debug("server was shutdown gracefully") | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err = hs.httpSrv.Serve(ln) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			hs.log.Debug("server was shutdown gracefully") | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		hs.log.Error("Invalid protocol", "protocol", setting.Protocol) | 
					
						
							|  |  |  | 		err = errors.New("Invalid Protocol") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-06 16:40:07 +08:00
										 |  |  | func (hs *HttpServer) Shutdown(ctx context.Context) error { | 
					
						
							|  |  |  | 	err := hs.httpSrv.Shutdown(ctx) | 
					
						
							|  |  |  | 	hs.log.Info("stopped http server") | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | func (hs *HttpServer) listenAndServeTLS(listenAddr, certfile, keyfile string) error { | 
					
						
							|  |  |  | 	if certfile == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("cert_file cannot be empty when using HTTPS") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if keyfile == "" { | 
					
						
							|  |  |  | 		return fmt.Errorf("cert_key cannot be empty when using HTTPS") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, err := os.Stat(setting.CertFile); os.IsNotExist(err) { | 
					
						
							|  |  |  | 		return fmt.Errorf(`Cannot find SSL cert_file at %v`, setting.CertFile) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, err := os.Stat(setting.KeyFile); os.IsNotExist(err) { | 
					
						
							|  |  |  | 		return fmt.Errorf(`Cannot find SSL key_file at %v`, setting.KeyFile) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-15 21:29:20 +08:00
										 |  |  | 	tlsCfg := &tls.Config{ | 
					
						
							|  |  |  | 		MinVersion:               tls.VersionTLS12, | 
					
						
							|  |  |  | 		PreferServerCipherSuites: true, | 
					
						
							|  |  |  | 		CipherSuites: []uint16{ | 
					
						
							|  |  |  | 			tls.TLS_RSA_WITH_AES_128_CBC_SHA, | 
					
						
							|  |  |  | 			tls.TLS_RSA_WITH_AES_256_CBC_SHA, | 
					
						
							|  |  |  | 			tls.TLS_RSA_WITH_AES_128_GCM_SHA256, | 
					
						
							|  |  |  | 			tls.TLS_RSA_WITH_AES_256_GCM_SHA384, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, | 
					
						
							|  |  |  | 			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	srv := &http.Server{ | 
					
						
							|  |  |  | 		Addr:         listenAddr, | 
					
						
							|  |  |  | 		Handler:      hs.macaron, | 
					
						
							|  |  |  | 		TLSConfig:    tlsCfg, | 
					
						
							|  |  |  | 		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return srv.ListenAndServeTLS(setting.CertFile, setting.KeyFile) | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (hs *HttpServer) newMacaron() *macaron.Macaron { | 
					
						
							|  |  |  | 	macaron.Env = setting.Env | 
					
						
							|  |  |  | 	m := macaron.New() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m.Use(middleware.Logger()) | 
					
						
							|  |  |  | 	m.Use(middleware.Recovery()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if setting.EnableGzip { | 
					
						
							|  |  |  | 		m.Use(middleware.Gziper()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, route := range plugins.StaticRoutes { | 
					
						
							|  |  |  | 		pluginRoute := path.Join("/public/plugins/", route.PluginId) | 
					
						
							|  |  |  | 		logger.Debug("Plugins: Adding route", "route", pluginRoute, "dir", route.Directory) | 
					
						
							|  |  |  | 		hs.mapStatic(m, route.Directory, "", pluginRoute) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hs.mapStatic(m, setting.StaticRootPath, "", "public") | 
					
						
							|  |  |  | 	hs.mapStatic(m, setting.StaticRootPath, "robots.txt", "robots.txt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m.Use(macaron.Renderer(macaron.RenderOptions{ | 
					
						
							|  |  |  | 		Directory:  path.Join(setting.StaticRootPath, "views"), | 
					
						
							|  |  |  | 		IndentJSON: macaron.Env != macaron.PROD, | 
					
						
							|  |  |  | 		Delims:     macaron.Delims{Left: "[[", Right: "]]"}, | 
					
						
							|  |  |  | 	})) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-25 23:17:45 +08:00
										 |  |  | 	m.Use(hs.healthHandler) | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 	m.Use(middleware.GetContextHandler()) | 
					
						
							|  |  |  | 	m.Use(middleware.Sessioner(&setting.SessionOptions)) | 
					
						
							|  |  |  | 	m.Use(middleware.RequestMetrics()) | 
					
						
							| 
									
										
										
										
											2017-02-17 22:02:14 +08:00
										 |  |  | 	m.Use(middleware.OrgRedirect()) | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// needs to be after context handler
 | 
					
						
							|  |  |  | 	if setting.EnforceDomain { | 
					
						
							|  |  |  | 		m.Use(middleware.ValidateHostHeader(setting.Domain)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return m | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-25 23:17:45 +08:00
										 |  |  | func (hs *HttpServer) healthHandler(ctx *macaron.Context) { | 
					
						
							|  |  |  | 	if ctx.Req.Method != "GET" || ctx.Req.URL.Path != "/api/health" { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data := simplejson.New() | 
					
						
							| 
									
										
										
										
											2017-04-25 23:23:38 +08:00
										 |  |  | 	data.Set("database", "ok") | 
					
						
							| 
									
										
										
										
											2017-04-25 23:17:45 +08:00
										 |  |  | 	data.Set("version", setting.BuildVersion) | 
					
						
							|  |  |  | 	data.Set("commit", setting.BuildCommit) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := bus.Dispatch(&models.GetDBHealthQuery{}); err != nil { | 
					
						
							| 
									
										
										
										
											2017-04-25 23:24:36 +08:00
										 |  |  | 		data.Set("database", "failing") | 
					
						
							| 
									
										
										
										
											2017-05-10 21:23:59 +08:00
										 |  |  | 		ctx.Resp.Header().Set("Content-Type", "application/json; charset=UTF-8") | 
					
						
							|  |  |  | 		ctx.Resp.WriteHeader(503) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ctx.Resp.Header().Set("Content-Type", "application/json; charset=UTF-8") | 
					
						
							|  |  |  | 		ctx.Resp.WriteHeader(200) | 
					
						
							| 
									
										
										
										
											2017-04-25 23:17:45 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dataBytes, _ := data.EncodePretty() | 
					
						
							|  |  |  | 	ctx.Resp.Write(dataBytes) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-12-21 21:36:32 +08:00
										 |  |  | func (hs *HttpServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) { | 
					
						
							|  |  |  | 	headers := func(c *macaron.Context) { | 
					
						
							|  |  |  | 		c.Resp.Header().Set("Cache-Control", "public, max-age=3600") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if setting.Env == setting.DEV { | 
					
						
							|  |  |  | 		headers = func(c *macaron.Context) { | 
					
						
							|  |  |  | 			c.Resp.Header().Set("Cache-Control", "max-age=0, must-revalidate, no-cache") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m.Use(httpstatic.Static( | 
					
						
							|  |  |  | 		path.Join(rootDir, dir), | 
					
						
							|  |  |  | 		httpstatic.StaticOptions{ | 
					
						
							|  |  |  | 			SkipLogging: true, | 
					
						
							|  |  |  | 			Prefix:      prefix, | 
					
						
							|  |  |  | 			AddHeaders:  headers, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	)) | 
					
						
							|  |  |  | } |