github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/web/app.go (about)

     1  package web
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"html/template"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"strings"
    13  
    14  	"github.com/codegangsta/inject"
    15  	"github.com/mongodb/grip"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  const JSONMarshalError = "Could not marshal data to JSON"
    20  
    21  type App struct {
    22  	Router         *http.Handler
    23  	TemplateFolder string
    24  	TemplateFuncs  map[string]interface{}
    25  	TemplateCache  map[string]*template.Template
    26  	CacheTemplates bool
    27  	inject.Injector
    28  }
    29  
    30  type HandlerApp interface {
    31  	RespondStreamTemplate(templateNames []string, entryPointName string, data interface{}) HTTPResponse
    32  	RespondTemplate(templateNames []string, entryPointName string, data interface{}) HTTPResponse
    33  	MakeHandler(handlers ...interface{}) http.HandlerFunc
    34  }
    35  
    36  func NewApp() *App {
    37  	return &App{
    38  		TemplateFuncs:  make(map[string]interface{}),
    39  		TemplateCache:  make(map[string]*template.Template),
    40  		CacheTemplates: true,
    41  		Injector:       inject.New(),
    42  	}
    43  }
    44  
    45  func (ae *App) RespondTemplate(templateNames []string, entryPointName string, data interface{}) HTTPResponse {
    46  	cacheKey := strings.Join(templateNames, ":") + ":" + entryPointName
    47  	tmpl := ae.TemplateCache[cacheKey]
    48  
    49  	if ae.CacheTemplates && tmpl != nil { //cache hit
    50  		return &TemplateResponse{entryPointName, tmpl, data, false}
    51  	} else { // cache miss.
    52  		templatePaths := make([]string, len(templateNames))
    53  		for i, v := range templateNames {
    54  			templatePaths[i] = filepath.Join(ae.TemplateFolder, v)
    55  		}
    56  		tmpl := template.New(templateNames[0]).Funcs(ae.TemplateFuncs)
    57  		tmpl, err := tmpl.ParseFiles(templatePaths...)
    58  		if err != nil {
    59  			grip.Error(errors.Wrapf(err, "encountered error parsing template %v", templatePaths))
    60  			return StringResponse{"Error: " + err.Error(), 500}
    61  		}
    62  		ae.TemplateCache[cacheKey] = tmpl
    63  		return &TemplateResponse{entryPointName, tmpl, data, false}
    64  	}
    65  }
    66  
    67  func (ae *App) RespondStreamTemplate(templateNames []string, entryPointName string, data interface{}) HTTPResponse {
    68  	cacheKey := strings.Join(templateNames, ":") + ":" + entryPointName
    69  	tmpl := ae.TemplateCache[cacheKey]
    70  
    71  	if tmpl != nil { //cache hit
    72  		return &TemplateResponse{entryPointName, tmpl, data, true}
    73  	} else { // cache miss.
    74  		templatePaths := make([]string, len(templateNames))
    75  		for i, v := range templateNames {
    76  			templatePaths[i] = filepath.Join(ae.TemplateFolder, v)
    77  		}
    78  		tmpl := template.New(templateNames[0]).Funcs(ae.TemplateFuncs)
    79  		tmpl, err := tmpl.ParseFiles(templatePaths...)
    80  		if err != nil {
    81  			return StringResponse{"Error: " + err.Error(), 500}
    82  		}
    83  		ae.TemplateCache[cacheKey] = tmpl
    84  		return &TemplateResponse{entryPointName, tmpl, data, true}
    85  	}
    86  }
    87  
    88  func (ae *App) MakeHandler(handlers ...interface{}) http.HandlerFunc {
    89  	return func(w http.ResponseWriter, r *http.Request) {
    90  		child := inject.New()
    91  		child.SetParent(ae.Injector)
    92  		child.MapTo(w, (*http.ResponseWriter)(nil))
    93  		child.Map(r)
    94  	handlerLoop:
    95  		for _, reqProcessor := range handlers {
    96  			result, err := child.Invoke(reqProcessor)
    97  			if err != nil {
    98  				w.WriteHeader(http.StatusInternalServerError)
    99  				fmt.Fprintf(os.Stderr, "error in url %v: %v\n", r.URL, err)
   100  				return
   101  			}
   102  
   103  			if len(result) == 0 {
   104  				continue
   105  			} else {
   106  				valAsInterface := result[0].Interface()
   107  				if resp, ok := valAsInterface.(HTTPResponse); ok {
   108  					err := resp.Render(w)
   109  					if err != nil {
   110  						w.WriteHeader(http.StatusInternalServerError)
   111  						fmt.Fprintln(os.Stderr, "error: %v", err)
   112  					}
   113  					if reflect.TypeOf(valAsInterface) != reflect.TypeOf(ChainHttpResponse{}) {
   114  						break handlerLoop
   115  					}
   116  				}
   117  			}
   118  		}
   119  	}
   120  }
   121  
   122  //HTTPResponse is an interface that is responsible for encapsulating how a response is rendered to an HTTP client.
   123  type HTTPResponse interface {
   124  	Render(w http.ResponseWriter) error
   125  }
   126  
   127  //StringResponse will write a simple string directly to the output
   128  type StringResponse struct {
   129  	Body       string
   130  	StatusCode int
   131  }
   132  
   133  //RawHTTPResponse will write a sequence of bytes directly to the output.
   134  type RawHTTPResponse []byte
   135  
   136  //JSONResponse will marshal "Data" as json to the output, and set StatusCode accordingly.
   137  type JSONResponse struct {
   138  	Data       interface{}
   139  	StatusCode int
   140  }
   141  
   142  //YAMLResponse will marshal "Data" as json to the output, and set StatusCode accordingly.
   143  type YAMLResponse struct {
   144  	Data       interface{}
   145  	StatusCode int
   146  }
   147  
   148  //TemplateResponse will execute TemplateSet against Data with the root template named by TemplateName.
   149  type TemplateResponse struct {
   150  	TemplateName string
   151  	TemplateSet  *template.Template
   152  	Data         interface{}
   153  	Stream       bool
   154  }
   155  
   156  //NullHttpResponse doesn't do anything to the response.
   157  //It can be used if the handler has already written the response directly
   158  //so no further action needs to be taken.
   159  type NullHttpResponse struct{}
   160  
   161  type ChainHttpResponse struct{}
   162  
   163  func (rw RawHTTPResponse) Render(w http.ResponseWriter) error {
   164  	_, err := w.Write(rw)
   165  	return err
   166  }
   167  
   168  func (nh NullHttpResponse) Render(w http.ResponseWriter) error {
   169  	return nil
   170  }
   171  
   172  func (nh ChainHttpResponse) Render(w http.ResponseWriter) error {
   173  	return nil
   174  }
   175  
   176  func (sr StringResponse) Render(w http.ResponseWriter) error {
   177  	if sr.StatusCode != 0 {
   178  		w.WriteHeader(sr.StatusCode)
   179  	}
   180  	_, err := w.Write([]byte(sr.Body))
   181  	return err
   182  }
   183  
   184  func (tr TemplateResponse) Render(w http.ResponseWriter) error {
   185  	var buf bytes.Buffer
   186  	if tr.Stream {
   187  		err := tr.TemplateSet.ExecuteTemplate(w, tr.TemplateName, tr.Data)
   188  		if err != nil {
   189  			w.WriteHeader(500)
   190  			w.Write([]byte(fmt.Sprintf("error: \"%v\"", err)))
   191  			return err
   192  		}
   193  	} else {
   194  		err := tr.TemplateSet.ExecuteTemplate(&buf, tr.TemplateName, tr.Data)
   195  		if err != nil {
   196  			w.WriteHeader(500)
   197  			w.Write([]byte(fmt.Sprintf("error: \"%v\"", err)))
   198  			return err
   199  		}
   200  		w.Write(buf.Bytes())
   201  	}
   202  	return nil
   203  }
   204  
   205  func (jr JSONResponse) Render(w http.ResponseWriter) error {
   206  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
   207  
   208  	statusCode := jr.StatusCode
   209  	jsonBytes, err := json.Marshal(jr.Data)
   210  	if err != nil {
   211  		grip.Errorf("marshaling json: %+v", err)
   212  		statusCode = http.StatusInternalServerError
   213  		jsonBytes = []byte(fmt.Sprintf(`{"error":"%v"}`, JSONMarshalError))
   214  	}
   215  
   216  	if statusCode != 0 {
   217  		w.WriteHeader(statusCode)
   218  	}
   219  
   220  	w.Write(jsonBytes)
   221  	return nil
   222  }