8d0d72dc191770f6810896306ea3f0f3beca64b5
1 //
2 // Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
18 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 // Package server implements the core of the SysDB web server.
27 package server
29 import (
30 "bytes"
31 "fmt"
32 "html/template"
33 "io"
34 "log"
35 "net/http"
36 "net/url"
37 "path/filepath"
38 "strings"
40 "github.com/sysdb/go/client"
41 )
43 // A Config specifies configuration values for a SysDB web server.
44 type Config struct {
45 // TemplatePath specifies the relative or absolute location of template files.
46 TemplatePath string
48 // StaticPath specifies the relative or absolute location of static files.
49 StaticPath string
50 }
52 // A Server implements an http.Handler that serves the SysDB user interface.
53 type Server struct {
54 c *client.Client
56 // Request multiplexer
57 mux map[string]handler
59 // Templates:
60 main *template.Template
61 results map[string]*template.Template
63 // Base directory of static files.
64 basedir string
65 }
67 // New constructs a new SysDB web server using the specified configuration.
68 func New(addr, user string, cfg Config) (*Server, error) {
69 s := &Server{results: make(map[string]*template.Template)}
71 var err error
72 if s.c, err = client.Connect(addr, user); err != nil {
73 return nil, err
74 }
75 if major, minor, patch, extra, err := s.c.ServerVersion(); err == nil {
76 log.Printf("Connected to SysDB %d.%d.%d%s.", major, minor, patch, extra)
77 }
79 if s.main, err = cfg.parse("main.tmpl"); err != nil {
80 return nil, err
81 }
82 types := []string{"host", "hosts", "service", "services", "metric", "metrics"}
83 for _, t := range types {
84 s.results[t], err = cfg.parse(t + ".tmpl")
85 if err != nil {
86 return nil, err
87 }
88 }
90 s.basedir = cfg.StaticPath
91 s.mux = map[string]handler{
92 "images": s.static,
93 "style": s.static,
94 "graph": s.graph,
95 }
96 return s, nil
97 }
99 func (cfg Config) parse(name string) (*template.Template, error) {
100 t := template.New(filepath.Base(name))
101 return t.ParseFiles(filepath.Join(cfg.TemplatePath, name))
102 }
104 type request struct {
105 r *http.Request
106 cmd string
107 args []string
108 }
110 type handler func(http.ResponseWriter, request)
112 type page struct {
113 Title string
114 Query string
115 Content template.HTML
116 }
118 // Content generators for HTML pages.
119 var content = map[string]func(request, *Server) (*page, error){
120 "": index,
122 // Queries
123 "host": fetch,
124 "service": fetch,
125 "metric": fetch,
126 "hosts": listAll,
127 "services": listAll,
128 "metrics": listAll,
129 "lookup": lookup,
130 }
132 // ServeHTTP implements the http.Handler interface and serves
133 // the SysDB user interface.
134 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
135 path := r.RequestURI
136 if len(path) > 0 && path[0] == '/' {
137 path = path[1:]
138 }
139 if idx := strings.Index(path, "?"); idx != -1 {
140 path = path[:idx]
141 }
142 var fields []string
143 for _, f := range strings.Split(path, "/") {
144 f, err := url.QueryUnescape(f)
145 if err != nil {
146 s.badrequest(w, fmt.Errorf("Error: %v", err))
147 return
148 }
149 fields = append(fields, f)
150 }
152 req := request{
153 r: r,
154 cmd: fields[0],
155 }
156 if len(fields) > 1 {
157 if fields[len(fields)-1] == "" {
158 // Slash at the end of the URL
159 fields = fields[:len(fields)-1]
160 }
161 if len(fields) > 1 {
162 req.args = fields[1:]
163 }
164 }
166 if h := s.mux[fields[0]]; h != nil {
167 h(w, req)
168 return
169 }
171 f, ok := content[req.cmd]
172 if !ok {
173 s.notfound(w, r)
174 return
175 }
176 r.ParseForm()
177 p, err := f(req, s)
178 if err != nil {
179 p = &page{
180 Content: "<section class=\"error\">" +
181 html(fmt.Sprintf("Error: %v", err)) +
182 "</section>",
183 }
184 }
186 p.Query = r.FormValue("query")
187 if p.Title == "" {
188 p.Title = "SysDB - The System Database"
189 }
191 var buf bytes.Buffer
192 err = s.main.Execute(&buf, p)
193 if err != nil {
194 s.internal(w, err)
195 return
196 }
198 w.WriteHeader(http.StatusOK)
199 io.Copy(w, &buf)
200 }
202 // static serves static content.
203 func (s *Server) static(w http.ResponseWriter, req request) {
204 http.ServeFile(w, req.r, filepath.Clean(filepath.Join(s.basedir, req.r.URL.Path)))
205 }
207 func index(_ request, s *Server) (*page, error) {
208 major, minor, patch, extra, err := s.c.ServerVersion()
209 if err != nil {
210 return nil, err
211 }
213 content := fmt.Sprintf("<section>"+
214 "<h1>Welcome to the System Database.</h1>"+
215 "<p>Connected to SysDB %d.%d.%d%s</p>"+
216 "</section>", major, minor, patch, html(extra))
217 return &page{Content: template.HTML(content)}, nil
218 }
220 func tmpl(t *template.Template, data interface{}) (*page, error) {
221 var buf bytes.Buffer
222 if err := t.Execute(&buf, data); err != nil {
223 return nil, fmt.Errorf("Template error: %v", err)
224 }
225 return &page{Content: template.HTML(buf.String())}, nil
226 }
228 func html(s string) template.HTML {
229 return template.HTML(template.HTMLEscapeString(s))
230 }
232 // vim: set tw=78 sw=4 sw=4 noexpandtab :