<commit_before>package main

import (
	"html/template"
	"net/http"
	"os"
	"fmt"
	"time"

	"github.com/Sirupsen/logrus"
)

// IndexGET handles GET requests for '/'
func IndexGET(w http.ResponseWriter, r *http.Request) {
	session, err := cookieStore.Get(r, "auth-session")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data := map[string]interface{}{
		"Page":    "Home",
		"Profile": session.Values["profile"],
	}
	renderTemplate(w, r, "index.tmpl", data)
}

// LoginGET handles GET requests for '/login'
func LoginGET(w http.ResponseWriter, r *http.Request) {
	data := map[string]interface{}{
		"Page":              "Login",
		"Auth0ClientId":     os.Getenv("AUTH0_CLIENT_ID"),
		"Auth0ClientSecret": os.Getenv("AUTH0_CLIENT_SECRET"),
		"Auth0Domain":       os.Getenv("AUTH0_DOMAIN"),
		"Auth0CallbackURL":  template.URL(os.Getenv("AUTH0_CALLBACK_URL")),
	}
	renderTemplate(w, r, "login.tmpl", data)
}

// CreateGET handles GET requests for '/create'
func CreateGET(w http.ResponseWriter, r *http.Request) {
	data := map[string]interface{}{
		"Page": "Create Event",
	}
	renderTemplate(w, r, "create.tmpl", data)
}

// CreatePOST handles POST requests for '/create'
func CreatePOST(w http.ResponseWriter, r *http.Request) {
	logrus.Info("Starting call")
	id, err := GetUserID(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	logrus.Info(id)

	r.ParseForm()
    title := r.Form["title"][0]
    eventType := r.Form["event_type"][0]
    eventTopic := r.Form["event_topic"][0]
    description := r.Form["description"][0]
    location := r.Form["location"][0]
    startDate := r.Form["start_date"][0]
    endDate := r.Form["end_date"][0]
    startTime := r.Form["start_time"][0]
    endTime := r.Form["end_time"][0]
    logrus.Info(r.Form)

    startTimeDate, err := time.Parse(dateTimeFormat, fmt.Sprintf("%s %s", startDate, startTime))
    if err != nil {
    	logrus.WithError(err).Error("Failed to parse date time")
    }
    endTimeDate, err := time.Parse(dateTimeFormat, fmt.Sprintf("%s %s", endDate, endTime))
    if err != nil {
    	logrus.WithError(err).Error("Failed to parse date time")
    }

	// TODO verify that answerA does not equal answerB
	query := `INSERT INTO event (
		        creator_id, title, start_timestamp, end_timestamp,
		        description, event_topic, event_type, location,
		        stars
			  )
			  VALUES (
			  	$1, $2, $3, $4,
			  	$5, $6, $7, $8,
			  	$9
			  )`
	_, err = ppdb.Exec(query, id, title, startTimeDate, endTimeDate,
		description, eventTopic, eventType, location,
		0)
	if err != nil {
		logrus.WithError(err).Error("Failed to save event")
	} else {
		logrus.Info("Successfully created new event")
	}
	//http.Redirect(w, r, "/", http.StatusRedirect)
	return
}

// EventsGET handles GET requests for '/events'
func EventsGET(w http.ResponseWriter, r *http.Request) {
	data := map[string]interface{}{
		"Page": "Events",
	}
	renderTemplate(w, r, "events.tmpl", data)
}
<commit_msg>fix redirect after closing modal<commit_after>package main

import (
	"html/template"
	"net/http"
	"os"
	"fmt"
	"time"

	"github.com/Sirupsen/logrus"
)

// IndexGET handles GET requests for '/'
func IndexGET(w http.ResponseWriter, r *http.Request) {
	session, err := cookieStore.Get(r, "auth-session")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	data := map[string]interface{}{
		"Page":    "Home",
		"Profile": session.Values["profile"],
	}
	renderTemplate(w, r, "index.tmpl", data)
}

// LoginGET handles GET requests for '/login'
func LoginGET(w http.ResponseWriter, r *http.Request) {
	data := map[string]interface{}{
		"Page":              "Login",
		"Auth0ClientId":     os.Getenv("AUTH0_CLIENT_ID"),
		"Auth0ClientSecret": os.Getenv("AUTH0_CLIENT_SECRET"),
		"Auth0Domain":       os.Getenv("AUTH0_DOMAIN"),
		"Auth0CallbackURL":  template.URL(os.Getenv("AUTH0_CALLBACK_URL")),
	}
	renderTemplate(w, r, "login.tmpl", data)
}

// CreateGET handles GET requests for '/create'
func CreateGET(w http.ResponseWriter, r *http.Request) {
	data := map[string]interface{}{
		"Page": "Create Event",
	}
	renderTemplate(w, r, "create.tmpl", data)
}

// CreatePOST handles POST requests for '/create'
func CreatePOST(w http.ResponseWriter, r *http.Request) {
	logrus.Info("Starting call")
	id, err := GetUserID(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	logrus.Info(id)

	r.ParseForm()
    title := r.Form["title"][0]
    eventType := r.Form["event_type"][0]
    eventTopic := r.Form["event_topic"][0]
    description := r.Form["description"][0]
    location := r.Form["location"][0]
    startDate := r.Form["start_date"][0]
    endDate := r.Form["end_date"][0]
    startTime := r.Form["start_time"][0]
    endTime := r.Form["end_time"][0]
    logrus.Info(r.Form)

    startTimeDate, err := time.Parse(dateTimeFormat, fmt.Sprintf("%s %s", startDate, startTime))
    if err != nil {
    	logrus.WithError(err).Error("Failed to parse date time")
    }
    endTimeDate, err := time.Parse(dateTimeFormat, fmt.Sprintf("%s %s", endDate, endTime))
    if err != nil {
    	logrus.WithError(err).Error("Failed to parse date time")
    }

	// TODO verify that answerA does not equal answerB
	query := `INSERT INTO event (
		        creator_id, title, start_timestamp, end_timestamp,
		        description, event_topic, event_type, location,
		        stars
			  )
			  VALUES (
			  	$1, $2, $3, $4,
			  	$5, $6, $7, $8,
			  	$9
			  )`
	_, err = ppdb.Exec(query, id, title, startTimeDate, endTimeDate,
		description, eventTopic, eventType, location,
		0)
	if err != nil {
		logrus.WithError(err).Error("Failed to save event")
	} else {
		logrus.Info("Successfully created new event")
	}
	EventsGET(w, r)
	return
}

// EventsGET handles GET requests for '/events'
func EventsGET(w http.ResponseWriter, r *http.Request) {
	data := map[string]interface{}{
		"Page": "Events",
	}
	renderTemplate(w, r, "events.tmpl", data)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

func Index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Welcome!")
}

func PostIndex(w http.ResponseWriter, r *http.Request) {
	var (
		posts   Posts
		id      int
		title   string
		content string
		posted  time.Time
	)

	db := dbConnection()
	rows, err := db.Query("SELECT * FROM posts")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {
		err := rows.Scan(&id, &title, &content, &posted)
		if err != nil {
			log.Fatal(err)
		}
		p := Post{id, title, content, posted}
		posts = append(posts, p)
	}
	err = rows.Err()
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	JSONHandler(w, r)
	if err := json.NewEncoder(w).Encode(posts); err != nil {
		log.Fatal(err)
	}
}

func PostShow(w http.ResponseWriter, r *http.Request) {
	var (
		p       Post
		id      int
		title   string
		content string
		posted  time.Time
	)

	vars := mux.Vars(r)
	postId := vars["postId"]

	db := dbConnection()
	rows, err := db.Query("SELECT * FROM posts WHERE ID = ?", postId)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {
		err := rows.Scan(&id, &title, &content, &posted)
		if err != nil {
			log.Fatal(err)
		}
		p = Post{id, title, content, posted}
	}
	err = rows.Err()
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	JSONHandler(w, r)
	if err := json.NewEncoder(w).Encode(p); err != nil {
		log.Fatal(err)
	}
}

func PostCreate(w http.ResponseWriter, r *http.Request) {

}
<commit_msg>Set up ability to send data to application<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

func Index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "Welcome!")
}

func PostIndex(w http.ResponseWriter, r *http.Request) {
	var (
		posts   Posts
		id      int
		title   string
		content string
		posted  time.Time
	)

	db := dbConnection()
	rows, err := db.Query("SELECT * FROM posts")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {
		err := rows.Scan(&id, &title, &content, &posted)
		if err != nil {
			log.Fatal(err)
		}
		p := Post{id, title, content, posted}
		posts = append(posts, p)
	}
	err = rows.Err()
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	JSONHandler(w, r)
	if err := json.NewEncoder(w).Encode(posts); err != nil {
		log.Fatal(err)
	}
}

func PostShow(w http.ResponseWriter, r *http.Request) {
	var (
		p       Post
		id      int
		title   string
		content string
		posted  time.Time
	)

	vars := mux.Vars(r)
	postId := vars["postId"]

	db := dbConnection()
	rows, err := db.Query("SELECT * FROM posts WHERE ID = ?", postId)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {
		err := rows.Scan(&id, &title, &content, &posted)
		if err != nil {
			log.Fatal(err)
		}
		p = Post{id, title, content, posted}
	}
	err = rows.Err()
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	JSONHandler(w, r)
	if err := json.NewEncoder(w).Encode(p); err != nil {
		log.Fatal(err)
	}
}

func PostCreate(w http.ResponseWriter, r *http.Request) {
	var post Post

	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	log.Print(body)
	if err != nil {
		log.Fatal(err)
	}
	if err := r.Body.Close(); err != nil {
		log.Fatal(err)
	}

	if err := json.Unmarshal(body, &post); err != nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(422)
	}
	log.Print(post)
}
<|endoftext|>


<commit_before>/*
Package handlers is a small library of utility http handlers that are useful
for building web applications. It includes a NotFoundHandler, an ErrorHandler,
and a FileHandler.

The NotFoundHandler and ErrorHandler provide a simple way to respond to the
client with custom 404 and 500 status pages: create your own application
specific error page templates and call each of the handler's Serve methods
with the appropriate arguments.

These two handlers are intended to be used indirectly, from inside other
handlers where server errors or page not found errors occur and you need
to report them to the browser. However, both of these types implement the
http.Handler interface with a ServeHTTP method, which shows their default
behaviour when bound to a specific route.

The FileHandler provides similar functionality to the FileServer in the
net/http package, but with two differences: it will not show directory
listings for directories under its path, and it will respond to any request
for a non-existent file with the given NotFoundHandler.
*/
package handlers

import (
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

// ErrorMessage holds the message passed to the error template. The template
// can access the message field with the {{.ErrorMessage}} tag.
type ErrorMessage struct {
	ErrorMessage string
}

// ErrorHandler serves error messages with the given template. The template
// can access the message served by the handler with the {{.ErrorMessage}} tag.
type ErrorHandler struct {
	template       *template.Template
	defaultMessage string
	displayErrors  bool
}

// NewErrorHandler returns a new ErrorHandler with the handler values initialised.
// The handler uses the given template to print an error message. The template
// must display {{.ErrorMessage}}. The default error message is set to message.
// The display argument controls whether error messages passed to the handler's
// ServeError function are shown to the user on the error page, or whether the
// default error message is shown instead. This allows detailed error messages
// to be printed to the screen during development, but turned off in production.
// The handler's AlwaysServeError method forces the display of a particular
// error message even if displayErrors is set to false.
func NewErrorHandler(template *template.Template, defaultMessage string, displayErrors bool) *ErrorHandler {

	return &ErrorHandler{
		template:       template,
		defaultMessage: defaultMessage,
		displayErrors:  displayErrors,
	}
}

// LoadErrorHandler is a convenience function that returns a new ErrorHandler
// using the template file specified by templatePath. The function first loads
// the template and then creates the ErrorHandler using NewErrorHandler.
func LoadErrorHandler(templatePath string, defaultMessage string, displayErrors bool) *ErrorHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewErrorHandler(template, defaultMessage, displayErrors)
}

// ServeError serves the appropriate error message in the error template
// depending on the value of displayErrors. If displayErrors is true then
// the given message is shown, otherwise the default error message is shown.
func (h *ErrorHandler) ServeError(w http.ResponseWriter, message string) {

	var templateData *ErrorMessage

	if h.displayErrors {

		templateData = &ErrorMessage{message}

	} else {

		templateData = &ErrorMessage{h.defaultMessage}
	}

	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// AlwaysServeError serves the given error message in the error template.
// This method overrides the default error message, irrespective of whether
// displayErrors is false, and ensures that the given message is always shown.
func (h *ErrorHandler) AlwaysServeError(w http.ResponseWriter, message string) {

	templateData := &ErrorMessage{message}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// ServeHTTP serves the default error message in the error template.
func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &ErrorMessage{h.defaultMessage}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// NotFoundData holds the path passed to the handler's template. The template
// can access the message field with the {{.Path}} tag.
type NotFoundData struct {
	Path string
}

// NotFoundHandler serves a 404 with the given template. The template
// can access the path to the file not found with {{.Path}} tag.
type NotFoundHandler struct {
	template *template.Template
}

// NewNotFoundHandler returns a new NotFoundHandler with the handler values
// initialised. The handler uses the given template to print the path to the
// file not found with a 404. The template must display {{.Path}}.
func NewNotFoundHandler(template *template.Template) *NotFoundHandler {

	return &NotFoundHandler{
		template: template,
	}
}

// LoadNotFoundHandler is a convenience function that returns a new NotFoundHandler
// using the template file specified by templatePath. The function first loads the
// template and then creates the NotFoundHandler using NewNotFoundHandler.
func LoadNotFoundHandler(templatePath string) *NotFoundHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewNotFoundHandler(template)
}

// ServeHTTP serves the path in the handler's template.
func (h *NotFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &NotFoundData{r.URL.Path}
	w.WriteHeader(http.StatusNotFound)
	h.template.Execute(w, templateData)
	return
}

// FileHandler serves files requested under the given url path from the given
// directory. The url path should be the same as the path to which the handler
// is bound with http.Handle. If the file is not found the handler serves a 404
// using the given notFoundHandler. The notFoundHandler can be any Handler, but
// its ServeHTTP method should return a 404. Unlike Go's built-in FileServer,
// FileHandler will not return directory listings for directories without an
// index.html and will instead respond with a 404.
type FileHandler struct {
	urlPath         string
	directory       string
	notFoundHandler http.Handler
}

// FileHandler returns a new FileHandler with the handler values initialised.
func NewFileHandler(urlPath string, directory string, notFoundHandler http.Handler) *FileHandler {

	return &FileHandler{
		urlPath:         urlPath,
		directory:       directory,
		notFoundHandler: notFoundHandler,
	}
}

// ServeHTTP is a wrapper around http.ServeFile, with paths and response
// values modified to provide the appropriate behaviour for the FileHandler.
func (h *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	const indexPage string = "index.html"

	var (
		requestPath string = r.URL.Path[len(h.urlPath)-1:]
		filePath    string
	)

	// If the request path ends in "/" ...
	if strings.HasSuffix(r.URL.Path, "/") {

		// Set the target filepath to index.html
		filePath = h.directory + filepath.FromSlash(requestPath+indexPage)

	} else {

		// Otherwise set the target filepath to the named file
		filePath = h.directory + filepath.FromSlash(requestPath)
	}

	// Try to get file info
	finfo, err := os.Stat(filePath)

	// If Stat fails return a 404
	if err != nil {

		h.notFoundHandler.ServeHTTP(w, r)
		return
	}

	// Check the mode to ensure the target filepath is a file
	switch mode := finfo.Mode(); {

	// If the target file is a directory redirect to the path with a slash
	case mode.IsDir():

		http.Redirect(w, r, r.URL.Path+"/", http.StatusFound)

	// Otherwise serve the file
	case mode.IsRegular():

		http.ServeFile(w, r, filePath)
	}

	return
}
<commit_msg>1.0.1<commit_after>/*
Package handlers is a small library of utility http handlers that are useful
for building web applications. It includes a NotFoundHandler, an ErrorHandler,
and a FileHandler.

The NotFoundHandler and ErrorHandler provide a simple way to respond to the
client with custom 404 and 500 status pages: create your own application
specific error page templates and call each of the handler's Serve methods
with the appropriate arguments.

These two handlers are intended to be used indirectly, from inside other
handlers where server errors or page not found errors occur and you need
to report them to the browser. However, both of these types implement the
http.Handler interface with a ServeHTTP method, which shows their default
behaviour when bound to a specific route.

The FileHandler provides similar functionality to the FileServer in the
net/http package, but with two differences: it will not show directory
listings for directories under its path, and it will respond to any request
for a non-existent file with the given NotFoundHandler.
*/
package handlers

import (
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

// ErrorMessage holds the message passed to the error template. The template
// can access the message field with the {{.ErrorMessage}} tag.
type ErrorMessage struct {
	ErrorMessage string
}

// ErrorHandler serves error messages with the given template. The template
// can access the message served by the handler with the {{.ErrorMessage}} tag.
type ErrorHandler struct {
	template       *template.Template
	defaultMessage string
	displayErrors  bool
}

// NewErrorHandler returns a new ErrorHandler with the handler values initialised.
// The handler uses the given template to print an error message. The template
// must display {{.ErrorMessage}}. The default error message is set to message.
// The display argument controls whether error messages passed to the handler's
// ServeError function are shown to the user on the error page, or whether the
// default error message is shown instead. This allows detailed error messages
// to be printed to the screen during development, but turned off in production.
// The handler's AlwaysServeError method forces the display of a particular
// error message even if displayErrors is set to false.
func NewErrorHandler(template *template.Template, defaultMessage string, displayErrors bool) *ErrorHandler {

	return &ErrorHandler{
		template:       template,
		defaultMessage: defaultMessage,
		displayErrors:  displayErrors,
	}
}

// LoadErrorHandler is a convenience function that returns a new ErrorHandler
// using the template file specified by templatePath. The function first loads
// the template and then creates the ErrorHandler using NewErrorHandler.
func LoadErrorHandler(templatePath string, defaultMessage string, displayErrors bool) *ErrorHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewErrorHandler(template, defaultMessage, displayErrors)
}

// ServeError serves the appropriate error message in the error template
// depending on the value of displayErrors. If displayErrors is true then
// the given message is shown, otherwise the default error message is shown.
func (h *ErrorHandler) ServeError(w http.ResponseWriter, message string) {

	var templateData *ErrorMessage

	if h.displayErrors {

		templateData = &ErrorMessage{message}

	} else {

		templateData = &ErrorMessage{h.defaultMessage}
	}

	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// AlwaysServeError serves the given error message in the error template.
// This method overrides the default error message, irrespective of whether
// displayErrors is false, and ensures that the given message is always shown.
func (h *ErrorHandler) AlwaysServeError(w http.ResponseWriter, message string) {

	templateData := &ErrorMessage{message}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// ServeHTTP serves the default error message in the error template.
func (h *ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &ErrorMessage{h.defaultMessage}
	w.WriteHeader(http.StatusInternalServerError)
	h.template.Execute(w, templateData)
	return
}

// NotFoundData holds the path passed to the handler's template. The template
// can access the message field with the {{.Path}} tag.
type NotFoundData struct {
	Path string
}

// NotFoundHandler serves a 404 with the given template. The template
// can access the path to the file not found with {{.Path}} tag.
type NotFoundHandler struct {
	template *template.Template
}

// NewNotFoundHandler returns a new NotFoundHandler with the handler values
// initialised. The handler uses the given template to print the path to the
// file not found with a 404. The template must display {{.Path}}.
func NewNotFoundHandler(template *template.Template) *NotFoundHandler {

	return &NotFoundHandler{
		template: template,
	}
}

// LoadNotFoundHandler is a convenience function that returns a new NotFoundHandler
// using the template file specified by templatePath. The function first loads the
// template and then creates the NotFoundHandler using NewNotFoundHandler.
func LoadNotFoundHandler(templatePath string) *NotFoundHandler {

	template, err := template.ParseFiles(templatePath)

	if err != nil {
		log.Fatal(err)
	}

	return NewNotFoundHandler(template)
}

// ServeHTTP serves the path in the handler's template.
func (h *NotFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	templateData := &NotFoundData{r.URL.Path}
	w.WriteHeader(http.StatusNotFound)
	h.template.Execute(w, templateData)
	return
}

// FileHandler serves files requested under the given url path from the given
// directory. The url path should be the same as the path to which the handler
// is bound with http.Handle. If the file is not found the handler serves a 404
// using the given notFoundHandler. The notFoundHandler can be any Handler, but
// its ServeHTTP method should return a 404. Unlike Go's built-in FileServer,
// FileHandler will not return directory listings for directories without an
// index.html and will instead respond with a 404.
type FileHandler struct {
	urlPath         string
	directory       string
	notFoundHandler http.Handler
}

// NewFileHandler returns a new FileHandler with the handler values initialised.
func NewFileHandler(urlPath string, directory string, notFoundHandler http.Handler) *FileHandler {

	return &FileHandler{
		urlPath:         urlPath,
		directory:       directory,
		notFoundHandler: notFoundHandler,
	}
}

// ServeHTTP is a wrapper around http.ServeFile, with paths and response
// values modified to provide the appropriate behaviour for the FileHandler.
func (h *FileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	const indexPage string = "index.html"

	var (
		requestPath string = r.URL.Path[len(h.urlPath)-1:]
		filePath    string
	)

	// If the request path ends in "/" ...
	if strings.HasSuffix(r.URL.Path, "/") {

		// Set the target filepath to index.html
		filePath = h.directory + filepath.FromSlash(requestPath+indexPage)

	} else {

		// Otherwise set the target filepath to the named file
		filePath = h.directory + filepath.FromSlash(requestPath)
	}

	// Try to get file info
	finfo, err := os.Stat(filePath)

	// If Stat fails return a 404
	if err != nil {

		h.notFoundHandler.ServeHTTP(w, r)
		return
	}

	// Check the mode to ensure the target filepath is a file
	switch mode := finfo.Mode(); {

	// If the target file is a directory redirect to the path with a slash
	case mode.IsDir():

		http.Redirect(w, r, r.URL.Path+"/", http.StatusFound)

	// Otherwise serve the file
	case mode.IsRegular():

		http.ServeFile(w, r, filePath)
	}

	return
}
<|endoftext|>


<commit_before>// Copyright 2013 The Gorilla 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 handlers

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

// MethodHandler is an http.Handler that dispatches to a handler whose key in the MethodHandler's
// map matches the name of the HTTP request's method, eg: GET
//
// If the request's method is OPTIONS and OPTIONS is not a key in the map then the handler
// responds with a status of 200 and sets the Allow header to a comma-separated list of
// available methods.
//
// If the request's method doesn't match any of its keys the handler responds with
// a status of 405, Method not allowed and sets the Allow header to a comma-separated list
// of available methods.
type MethodHandler map[string]http.Handler

func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if handler, ok := h[req.Method]; ok {
		handler.ServeHTTP(w, req)
	} else {
		allow := []string{}
		for k := range h {
			allow = append(allow, k)
		}
		sort.Strings(allow)
		w.Header().Set("Allow", strings.Join(allow, ", "))
		if req.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
		} else {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		}
	}
}

// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
type loggingHandler struct {
	writer  io.Writer
	handler http.Handler
}

// combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
type combinedLoggingHandler struct {
	writer  io.Writer
	handler http.Handler
}

func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	t := time.Now()
	logger := makeLogger(w)
	url := *req.URL
	h.handler.ServeHTTP(logger, req)
	writeLog(h.writer, req, url, t, logger.Status(), logger.Size())
}

func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	t := time.Now()
	logger := makeLogger(w)
	url := *req.URL
	h.handler.ServeHTTP(logger, req)
	writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size())
}

func makeLogger(w http.ResponseWriter) loggingResponseWriter {
	var logger loggingResponseWriter = &responseLogger{w: w}
	if _, ok := w.(http.Hijacker); ok {
		logger = &hijackLogger{responseLogger{w: w}}
	}
	h, ok1 := logger.(http.Hijacker)
	c, ok2 := w.(http.CloseNotifier)
	if ok1 && ok2 {
		return hijackCloseNotifier{logger, h, c}
	}
	if ok2 {
		return &closeNotifyWriter{logger, c}
	}
	return logger
}

type loggingResponseWriter interface {
	http.ResponseWriter
	http.Flusher
	Status() int
	Size() int
}

// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
// code and body size
type responseLogger struct {
	w      http.ResponseWriter
	status int
	size   int
}

func (l *responseLogger) Header() http.Header {
	return l.w.Header()
}

func (l *responseLogger) Write(b []byte) (int, error) {
	if l.status == 0 {
		// The status will be StatusOK if WriteHeader has not been called yet
		l.status = http.StatusOK
	}
	size, err := l.w.Write(b)
	l.size += size
	return size, err
}

func (l *responseLogger) WriteHeader(s int) {
	l.w.WriteHeader(s)
	l.status = s
}

func (l *responseLogger) Status() int {
	return l.status
}

func (l *responseLogger) Size() int {
	return l.size
}

func (l *responseLogger) Flush() {
	f, ok := l.w.(http.Flusher)
	if ok {
		f.Flush()
	}
}

type hijackLogger struct {
	responseLogger
}

func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h := l.responseLogger.w.(http.Hijacker)
	conn, rw, err := h.Hijack()
	if err == nil && l.responseLogger.status == 0 {
		// The status will be StatusSwitchingProtocols if there was no error and WriteHeader has not been called yet
		l.responseLogger.status = http.StatusSwitchingProtocols
	}
	return conn, rw, err
}

type closeNotifyWriter struct {
	loggingResponseWriter
	http.CloseNotifier
}

type hijackCloseNotifier struct {
	loggingResponseWriter
	http.Hijacker
	http.CloseNotifier
}

const lowerhex = "0123456789abcdef"

func appendQuoted(buf []byte, s string) []byte {
	var runeTmp [utf8.UTFMax]byte
	for width := 0; len(s) > 0; s = s[width:] {
		r := rune(s[0])
		width = 1
		if r >= utf8.RuneSelf {
			r, width = utf8.DecodeRuneInString(s)
		}
		if width == 1 && r == utf8.RuneError {
			buf = append(buf, `\x`...)
			buf = append(buf, lowerhex[s[0]>>4])
			buf = append(buf, lowerhex[s[0]&0xF])
			continue
		}
		if r == rune('"') || r == '\\' { // always backslashed
			buf = append(buf, '\\')
			buf = append(buf, byte(r))
			continue
		}
		if strconv.IsPrint(r) {
			n := utf8.EncodeRune(runeTmp[:], r)
			buf = append(buf, runeTmp[:n]...)
			continue
		}
		switch r {
		case '\a':
			buf = append(buf, `\a`...)
		case '\b':
			buf = append(buf, `\b`...)
		case '\f':
			buf = append(buf, `\f`...)
		case '\n':
			buf = append(buf, `\n`...)
		case '\r':
			buf = append(buf, `\r`...)
		case '\t':
			buf = append(buf, `\t`...)
		case '\v':
			buf = append(buf, `\v`...)
		default:
			switch {
			case r < ' ':
				buf = append(buf, `\x`...)
				buf = append(buf, lowerhex[s[0]>>4])
				buf = append(buf, lowerhex[s[0]&0xF])
			case r > utf8.MaxRune:
				r = 0xFFFD
				fallthrough
			case r < 0x10000:
				buf = append(buf, `\u`...)
				for s := 12; s >= 0; s -= 4 {
					buf = append(buf, lowerhex[r>>uint(s)&0xF])
				}
			default:
				buf = append(buf, `\U`...)
				for s := 28; s >= 0; s -= 4 {
					buf = append(buf, lowerhex[r>>uint(s)&0xF])
				}
			}
		}
	}
	return buf

}

// buildCommonLogLine builds a log entry for req in Apache Common Log Format.
// ts is the timestamp with which the entry should be logged.
// status and size are used to provide the response HTTP status and size.
func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
	username := "-"
	if url.User != nil {
		if name := url.User.Username(); name != "" {
			username = name
		}
	}

	host, _, err := net.SplitHostPort(req.RemoteAddr)

	if err != nil {
		host = req.RemoteAddr
	}

	uri := url.RequestURI()

	buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
	buf = append(buf, host...)
	buf = append(buf, " - "...)
	buf = append(buf, username...)
	buf = append(buf, " ["...)
	buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
	buf = append(buf, `] "`...)
	buf = append(buf, req.Method...)
	buf = append(buf, " "...)
	buf = appendQuoted(buf, uri)
	buf = append(buf, " "...)
	buf = append(buf, req.Proto...)
	buf = append(buf, `" `...)
	buf = append(buf, strconv.Itoa(status)...)
	buf = append(buf, " "...)
	buf = append(buf, strconv.Itoa(size)...)
	return buf
}

// writeLog writes a log entry for req to w in Apache Common Log Format.
// ts is the timestamp with which the entry should be logged.
// status and size are used to provide the response HTTP status and size.
func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
	buf := buildCommonLogLine(req, url, ts, status, size)
	buf = append(buf, '\n')
	w.Write(buf)
}

// writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
// ts is the timestamp with which the entry should be logged.
// status and size are used to provide the response HTTP status and size.
func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
	buf := buildCommonLogLine(req, url, ts, status, size)
	buf = append(buf, ` "`...)
	buf = appendQuoted(buf, req.Referer())
	buf = append(buf, `" "`...)
	buf = appendQuoted(buf, req.UserAgent())
	buf = append(buf, '"', '\n')
	w.Write(buf)
}

// CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
// Apache Combined Log Format.
//
// See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
//
// LoggingHandler always sets the ident field of the log to -
func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
	return combinedLoggingHandler{out, h}
}

// LoggingHandler return a http.Handler that wraps h and logs requests to out in
// Apache Common Log Format (CLF).
//
// See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
//
// LoggingHandler always sets the ident field of the log to -
//
// Example:
//
//  r := mux.NewRouter()
//  r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
//  	w.Write([]byte("This is a catch-all route"))
//  })
//  loggedRouter := handlers.LoggingHandler(os.Stdout, r)
//  http.ListenAndServe(":1123", loggedRouter)
//
func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
	return loggingHandler{out, h}
}

// isContentType validates the Content-Type header
// is contentType. That is, its type and subtype match.
func isContentType(h http.Header, contentType string) bool {
	ct := h.Get("Content-Type")
	if i := strings.IndexRune(ct, ';'); i != -1 {
		ct = ct[0:i]
	}
	return ct == contentType
}

// ContentTypeHandler wraps and returns a http.Handler, validating the request content type
// is acompatible with the contentTypes list.
// It writes a HTTP 415 error if that fails.
//
// Only PUT, POST, and PATCH requests are considered.
func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") {
			h.ServeHTTP(w, r)
			return
		}

		for _, ct := range contentTypes {
			if isContentType(r.Header, ct) {
				h.ServeHTTP(w, r)
				return
			}
		}
		http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType)
	})
}

const (
	// HTTPMethodOverrideHeader is a commonly used
	// http header to override a request method.
	HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
	// HTTPMethodOverrideFormKey is a commonly used
	// HTML form key to override a request method.
	HTTPMethodOverrideFormKey = "_method"
)

// HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for the X-HTTP-Method-Override header
// or the _method form key, and overrides (if valid) request.Method with its value.
//
// This is especially useful for http clients that don't support many http verbs.
// It isn't secure to override e.g a GET to a POST, so only POST requests are considered.
// Likewise, the override method can only be a "write" method: PUT, PATCH or DELETE.
//
// Form method takes precedence over header method.
func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "POST" {
			om := r.FormValue(HTTPMethodOverrideFormKey)
			if om == "" {
				om = r.Header.Get(HTTPMethodOverrideHeader)
			}
			if om == "PUT" || om == "PATCH" || om == "DELETE" {
				r.Method = om
			}
		}
		h.ServeHTTP(w, r)
	})
}
<commit_msg>log right requestURI in proxy mode<commit_after>// Copyright 2013 The Gorilla 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 handlers

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

// MethodHandler is an http.Handler that dispatches to a handler whose key in the MethodHandler's
// map matches the name of the HTTP request's method, eg: GET
//
// If the request's method is OPTIONS and OPTIONS is not a key in the map then the handler
// responds with a status of 200 and sets the Allow header to a comma-separated list of
// available methods.
//
// If the request's method doesn't match any of its keys the handler responds with
// a status of 405, Method not allowed and sets the Allow header to a comma-separated list
// of available methods.
type MethodHandler map[string]http.Handler

func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if handler, ok := h[req.Method]; ok {
		handler.ServeHTTP(w, req)
	} else {
		allow := []string{}
		for k := range h {
			allow = append(allow, k)
		}
		sort.Strings(allow)
		w.Header().Set("Allow", strings.Join(allow, ", "))
		if req.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
		} else {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		}
	}
}

// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
type loggingHandler struct {
	writer  io.Writer
	handler http.Handler
}

// combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
type combinedLoggingHandler struct {
	writer  io.Writer
	handler http.Handler
}

func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	t := time.Now()
	logger := makeLogger(w)
	url := *req.URL
	h.handler.ServeHTTP(logger, req)
	writeLog(h.writer, req, url, t, logger.Status(), logger.Size())
}

func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	t := time.Now()
	logger := makeLogger(w)
	url := *req.URL
	h.handler.ServeHTTP(logger, req)
	writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size())
}

func makeLogger(w http.ResponseWriter) loggingResponseWriter {
	var logger loggingResponseWriter = &responseLogger{w: w}
	if _, ok := w.(http.Hijacker); ok {
		logger = &hijackLogger{responseLogger{w: w}}
	}
	h, ok1 := logger.(http.Hijacker)
	c, ok2 := w.(http.CloseNotifier)
	if ok1 && ok2 {
		return hijackCloseNotifier{logger, h, c}
	}
	if ok2 {
		return &closeNotifyWriter{logger, c}
	}
	return logger
}

type loggingResponseWriter interface {
	http.ResponseWriter
	http.Flusher
	Status() int
	Size() int
}

// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
// code and body size
type responseLogger struct {
	w      http.ResponseWriter
	status int
	size   int
}

func (l *responseLogger) Header() http.Header {
	return l.w.Header()
}

func (l *responseLogger) Write(b []byte) (int, error) {
	if l.status == 0 {
		// The status will be StatusOK if WriteHeader has not been called yet
		l.status = http.StatusOK
	}
	size, err := l.w.Write(b)
	l.size += size
	return size, err
}

func (l *responseLogger) WriteHeader(s int) {
	l.w.WriteHeader(s)
	l.status = s
}

func (l *responseLogger) Status() int {
	return l.status
}

func (l *responseLogger) Size() int {
	return l.size
}

func (l *responseLogger) Flush() {
	f, ok := l.w.(http.Flusher)
	if ok {
		f.Flush()
	}
}

type hijackLogger struct {
	responseLogger
}

func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h := l.responseLogger.w.(http.Hijacker)
	conn, rw, err := h.Hijack()
	if err == nil && l.responseLogger.status == 0 {
		// The status will be StatusSwitchingProtocols if there was no error and WriteHeader has not been called yet
		l.responseLogger.status = http.StatusSwitchingProtocols
	}
	return conn, rw, err
}

type closeNotifyWriter struct {
	loggingResponseWriter
	http.CloseNotifier
}

type hijackCloseNotifier struct {
	loggingResponseWriter
	http.Hijacker
	http.CloseNotifier
}

const lowerhex = "0123456789abcdef"

func appendQuoted(buf []byte, s string) []byte {
	var runeTmp [utf8.UTFMax]byte
	for width := 0; len(s) > 0; s = s[width:] {
		r := rune(s[0])
		width = 1
		if r >= utf8.RuneSelf {
			r, width = utf8.DecodeRuneInString(s)
		}
		if width == 1 && r == utf8.RuneError {
			buf = append(buf, `\x`...)
			buf = append(buf, lowerhex[s[0]>>4])
			buf = append(buf, lowerhex[s[0]&0xF])
			continue
		}
		if r == rune('"') || r == '\\' { // always backslashed
			buf = append(buf, '\\')
			buf = append(buf, byte(r))
			continue
		}
		if strconv.IsPrint(r) {
			n := utf8.EncodeRune(runeTmp[:], r)
			buf = append(buf, runeTmp[:n]...)
			continue
		}
		switch r {
		case '\a':
			buf = append(buf, `\a`...)
		case '\b':
			buf = append(buf, `\b`...)
		case '\f':
			buf = append(buf, `\f`...)
		case '\n':
			buf = append(buf, `\n`...)
		case '\r':
			buf = append(buf, `\r`...)
		case '\t':
			buf = append(buf, `\t`...)
		case '\v':
			buf = append(buf, `\v`...)
		default:
			switch {
			case r < ' ':
				buf = append(buf, `\x`...)
				buf = append(buf, lowerhex[s[0]>>4])
				buf = append(buf, lowerhex[s[0]&0xF])
			case r > utf8.MaxRune:
				r = 0xFFFD
				fallthrough
			case r < 0x10000:
				buf = append(buf, `\u`...)
				for s := 12; s >= 0; s -= 4 {
					buf = append(buf, lowerhex[r>>uint(s)&0xF])
				}
			default:
				buf = append(buf, `\U`...)
				for s := 28; s >= 0; s -= 4 {
					buf = append(buf, lowerhex[r>>uint(s)&0xF])
				}
			}
		}
	}
	return buf

}

// buildCommonLogLine builds a log entry for req in Apache Common Log Format.
// ts is the timestamp with which the entry should be logged.
// status and size are used to provide the response HTTP status and size.
func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
	username := "-"
	if url.User != nil {
		if name := url.User.Username(); name != "" {
			username = name
		}
	}

	host, _, err := net.SplitHostPort(req.RemoteAddr)

	if err != nil {
		host = req.RemoteAddr
	}

	uri := req.RequestURI
	if req.ProtoMajor == 2 && req.Method == "CONNECT" {
		uri = req.Host
	}

	buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
	buf = append(buf, host...)
	buf = append(buf, " - "...)
	buf = append(buf, username...)
	buf = append(buf, " ["...)
	buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
	buf = append(buf, `] "`...)
	buf = append(buf, req.Method...)
	buf = append(buf, " "...)
	buf = appendQuoted(buf, uri)
	buf = append(buf, " "...)
	buf = append(buf, req.Proto...)
	buf = append(buf, `" `...)
	buf = append(buf, strconv.Itoa(status)...)
	buf = append(buf, " "...)
	buf = append(buf, strconv.Itoa(size)...)
	return buf
}

// writeLog writes a log entry for req to w in Apache Common Log Format.
// ts is the timestamp with which the entry should be logged.
// status and size are used to provide the response HTTP status and size.
func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
	buf := buildCommonLogLine(req, url, ts, status, size)
	buf = append(buf, '\n')
	w.Write(buf)
}

// writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
// ts is the timestamp with which the entry should be logged.
// status and size are used to provide the response HTTP status and size.
func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
	buf := buildCommonLogLine(req, url, ts, status, size)
	buf = append(buf, ` "`...)
	buf = appendQuoted(buf, req.Referer())
	buf = append(buf, `" "`...)
	buf = appendQuoted(buf, req.UserAgent())
	buf = append(buf, '"', '\n')
	w.Write(buf)
}

// CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
// Apache Combined Log Format.
//
// See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
//
// LoggingHandler always sets the ident field of the log to -
func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
	return combinedLoggingHandler{out, h}
}

// LoggingHandler return a http.Handler that wraps h and logs requests to out in
// Apache Common Log Format (CLF).
//
// See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
//
// LoggingHandler always sets the ident field of the log to -
//
// Example:
//
//  r := mux.NewRouter()
//  r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
//  	w.Write([]byte("This is a catch-all route"))
//  })
//  loggedRouter := handlers.LoggingHandler(os.Stdout, r)
//  http.ListenAndServe(":1123", loggedRouter)
//
func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
	return loggingHandler{out, h}
}

// isContentType validates the Content-Type header
// is contentType. That is, its type and subtype match.
func isContentType(h http.Header, contentType string) bool {
	ct := h.Get("Content-Type")
	if i := strings.IndexRune(ct, ';'); i != -1 {
		ct = ct[0:i]
	}
	return ct == contentType
}

// ContentTypeHandler wraps and returns a http.Handler, validating the request content type
// is acompatible with the contentTypes list.
// It writes a HTTP 415 error if that fails.
//
// Only PUT, POST, and PATCH requests are considered.
func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") {
			h.ServeHTTP(w, r)
			return
		}

		for _, ct := range contentTypes {
			if isContentType(r.Header, ct) {
				h.ServeHTTP(w, r)
				return
			}
		}
		http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType)
	})
}

const (
	// HTTPMethodOverrideHeader is a commonly used
	// http header to override a request method.
	HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
	// HTTPMethodOverrideFormKey is a commonly used
	// HTML form key to override a request method.
	HTTPMethodOverrideFormKey = "_method"
)

// HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for the X-HTTP-Method-Override header
// or the _method form key, and overrides (if valid) request.Method with its value.
//
// This is especially useful for http clients that don't support many http verbs.
// It isn't secure to override e.g a GET to a POST, so only POST requests are considered.
// Likewise, the override method can only be a "write" method: PUT, PATCH or DELETE.
//
// Form method takes precedence over header method.
func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "POST" {
			om := r.FormValue(HTTPMethodOverrideFormKey)
			if om == "" {
				om = r.Header.Get(HTTPMethodOverrideHeader)
			}
			if om == "PUT" || om == "PATCH" || om == "DELETE" {
				r.Method = om
			}
		}
		h.ServeHTTP(w, r)
	})
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package main

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"time"

	log "github.com/sirupsen/logrus"
	"go.mozilla.org/autograph/formats"
	"go.mozilla.org/autograph/signer"
)

// HeartbeatConfig configures the heartbeat handler. It sets timeouts
// for each backing service to check.
//
// `hsmHeartbeatSignerConf` is determined added on boot in initHSM
type heartbeatConfig struct {
	HSMCheckTimeout time.Duration
	DBCheckTimeout  time.Duration

	// hsmSignerConf is the signer conf to use to check
	// HSM connectivity (set to the first signer with an HSM label
	// in initHSM) when it is non-nil
	hsmSignerConf *signer.Configuration
}

// hashSHA256AsHex returns the hex encoded string of the SHA256 sum
// the arg toHash bytes
func hashSHA256AsHex(toHash []byte) string {
	h := sha256.New()
	h.Write(toHash)
	return asHex(h.Sum(nil))
}

func asHex(toHex []byte) string {
	return fmt.Sprintf("%X", toHex)
}

// handleSignature endpoint accepts a list of signature requests in a HAWK authenticated POST request
// and calls the signers to generate signature responses.
func (a *autographer) handleSignature(w http.ResponseWriter, r *http.Request) {
	rid := getRequestID(r)
	starttime := getRequestStartTime(r)
	auth, userid, err := a.authorizeHeader(r)
	if err != nil {
		if a.stats != nil {
			sendStatsErr := a.stats.Timing("hawk.authorize_header_failed", time.Since(starttime), nil, 1.0)
			if sendStatsErr != nil {
				log.Warnf("Error sending hawk.authorize_header_failed: %s", sendStatsErr)
			}
		}
		httpError(w, r, http.StatusUnauthorized, "authorization verification failed: %v", err)
		return
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		httpError(w, r, http.StatusBadRequest, "failed to read request body: %s", err)
		return
	}
	if r.Header.Get("Content-Type") != "application/json" {
		httpError(w, r, http.StatusBadRequest, "invalid content type, expected application/json")
		return
	}
	if len(body) < 10 {
		// it's impossible to have a valid request body smaller than 10 bytes
		httpError(w, r, http.StatusBadRequest, "empty or invalid request request body")
		return
	}
	if len(body) > 1048576000 {
		// the max body size is hardcoded to 1GB. Seriously, what are you trying to sign?
		httpError(w, r, http.StatusBadRequest, "request exceeds max size of 1GB")
		return
	}
	err = a.authorizeBody(auth, r, body)
	if a.stats != nil {
		sendStatsErr := a.stats.Timing("authorize_finished", time.Since(starttime), nil, 1.0)
		if sendStatsErr != nil {
			log.Warnf("Error sending authorize_finished: %s", sendStatsErr)
		}
	}
	if err != nil {
		httpError(w, r, http.StatusUnauthorized, "authorization verification failed: %v", err)
		return
	}
	var sigreqs []formats.SignatureRequest
	err = json.Unmarshal(body, &sigreqs)
	if a.stats != nil {
		sendStatsErr := a.stats.Timing("body_unmarshaled", time.Since(starttime), nil, 1.0)
		if sendStatsErr != nil {
			log.Warnf("Error sending body_unmarshaled: %s", sendStatsErr)
		}
	}
	if err != nil {
		httpError(w, r, http.StatusBadRequest, "failed to parse request body: %v", err)
		return
	}
	for i, sigreq := range sigreqs {
		if sigreq.Input == "" {
			httpError(w, r, http.StatusBadRequest, fmt.Sprintf("missing input in signature request %d", i))
		}
	}
	if a.debug {
		fmt.Printf("signature request\n-----------------\n%s\n", body)
	}
	sigresps := make([]formats.SignatureResponse, len(sigreqs))
	// Each signature requested in the http request body is processed individually.
	// For each, a signer is looked up, and used to compute a raw signature
	// the signature is then encoded appropriately, and added to the response slice
	for i, sigreq := range sigreqs {
		var (
			input      []byte
			sig        signer.Signature
			signedfile []byte
			hashlog    string
		)

		// Decode the base64 input data
		input, err = base64.StdEncoding.DecodeString(sigreq.Input)
		if err != nil {
			httpError(w, r, http.StatusBadRequest, "%v", err)
			return
		}

		// Find the ID of the requested signer
		// Return an error if the signer is not found or if the user is not allowed
		// to use this signer
		signerID, err := a.getSignerID(userid, sigreq.KeyID)
		if err != nil || signerID < 0 {
			httpError(w, r, http.StatusUnauthorized, "%v", err)
			return
		}
		sigresps[i] = formats.SignatureResponse{
			Ref:        id(),
			Type:       a.signers[signerID].Config().Type,
			Mode:       a.signers[signerID].Config().Mode,
			SignerID:   a.signers[signerID].Config().ID,
			PublicKey:  a.signers[signerID].Config().PublicKey,
			SignedFile: base64.StdEncoding.EncodeToString(signedfile),
			X5U:        a.signers[signerID].Config().X5U,
			SignerOpts: a.signers[signerID].Config().SignerOpts,
		}
		// Make sure the signer implements the right interface, then sign the data
		switch r.URL.RequestURI() {
		case "/sign/hash":
			hashSigner, ok := a.signers[signerID].(signer.HashSigner)
			if !ok {
				httpError(w, r, http.StatusBadRequest, "requested signer does not implement hash signing")
				return
			}
			sig, err = hashSigner.SignHash(input, sigreq.Options)
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
				return
			}
			sigresps[i].Signature, err = sig.(signer.Signature).Marshal()
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "encoding failed with error: %v", err)
				return
			}
			// the input is already a hash just convert it to hex
			inputHash := asHex(input)
		case "/sign/data":
			dataSigner, ok := a.signers[signerID].(signer.DataSigner)
			if !ok {
				httpError(w, r, http.StatusBadRequest, "requested signer does not implement data signing")
				return
			}
			sig, err = dataSigner.SignData(input, sigreq.Options)
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
				return
			}
			sigresps[i].Signature, err = sig.(signer.Signature).Marshal()
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "encoding failed with error: %v", err)
				return
			}
			// calculate a hash of the input to store in the signing logs
			inputHash := hashSHA256AsHex(input)
		case "/sign/file":
			fileSigner, ok := a.signers[signerID].(signer.FileSigner)
			if !ok {
				httpError(w, r, http.StatusBadRequest, "requested signer does not implement file signing")
				return
			}
			signedfile, err = fileSigner.SignFile(input, sigreq.Options)
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
				return
			}
			sigresps[i].SignedFile = base64.StdEncoding.EncodeToString(signedfile)
			// calculate a hash of the input to store in the signing logs
			inputHash := hashSHA256AsHex(input)
		}
		log.WithFields(log.Fields{
			"rid":        rid,
			"options":    sigreq.Options,
			"mode":       sigresps[i].Mode,
			"ref":        sigresps[i].Ref,
			"type":       sigresps[i].Type,
			"signer_id":  sigresps[i].SignerID,
			"input_hash": inputHash,
			"user_id":    userid,
			"t":          int32(time.Since(starttime) / time.Millisecond), //  request processing time in ms
		}).Info("signing operation succeeded")
	}
	respdata, err := json.Marshal(sigresps)
	if err != nil {
		httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
		return
	}
	if a.debug {
		fmt.Printf("signature response\n------------------\n%s\n", respdata)
	}
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write(respdata)
	log.WithFields(log.Fields{"rid": rid}).Info("signing request completed successfully")
}

// handleLBHeartbeat returns a simple message indicating that the API is alive and well
func handleLBHeartbeat(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, r, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	w.Write([]byte("ohai"))
}

// handleHeartbeat checks whether backing services are enabled and
// accessible and returns 200 when they are and 502 when the
// aren't. Currently it only checks whether the HSM is accessible.
func (a *autographer) handleHeartbeat(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, r, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	if a.heartbeatConf == nil {
		httpError(w, r, http.StatusInternalServerError, "Missing heartbeat config")
		return
	}
	var (
		// a map of backing service name to up or down/inaccessible status
		result         = map[string]bool{}
		status         = http.StatusOK
		requestContext = r.Context()
		rid            = getRequestID(r)
	)

	// try to fetch the private key from the HSM for the first
	// signer conf with a non-PEM private key that we saved on
	// server start
	if a.heartbeatConf.hsmSignerConf != nil {
		var (
			err                 error
			hsmSignerConf       = a.heartbeatConf.hsmSignerConf
			hsmHBTimeout        = a.heartbeatConf.HSMCheckTimeout
			checkResult         = make(chan error, 1)
			hsmHeartbeatStartTs = time.Now()
		)
		go func() {
			checkResult <- hsmSignerConf.CheckHSMConnection()
		}()
		select {
		case <-time.After(hsmHBTimeout):
			err = fmt.Errorf("Checking HSM connection for signer %s private key timed out", hsmSignerConf.ID)
		case err = <-checkResult:
		}

		if err == nil {
			log.WithFields(log.Fields{
				"rid":     rid,
				"t":       int32(time.Since(hsmHeartbeatStartTs) / time.Millisecond),
				"timeout": fmt.Sprintf("%s", hsmHBTimeout),
			}).Info("HSM heartbeat completed successfully")
			result["hsmAccessible"] = true
			status = http.StatusOK
		} else {
			log.Errorf("error checking HSM connection for signer %s: %s", hsmSignerConf.ID, err)
			result["hsmAccessible"] = false
			status = http.StatusInternalServerError
		}
	}

	// check the database connection and return its status, but
	// don't fail the heartbeat since we only care about DB
	// connectivity on server start
	if a.db != nil {
		dbHeartbeatStartTs := time.Now()
		dbCheckCtx, dbCancel := context.WithTimeout(requestContext, a.heartbeatConf.DBCheckTimeout)
		defer dbCancel()
		err := a.db.CheckConnectionContext(dbCheckCtx)
		if err == nil {
			log.WithFields(log.Fields{
				"rid":     rid,
				"t":       int32(time.Since(dbHeartbeatStartTs) / time.Millisecond),
				"timeout": fmt.Sprintf("%s", a.heartbeatConf.DBCheckTimeout),
			}).Info("DB heartbeat completed successfully")
			result["dbAccessible"] = true
		} else {
			log.Errorf("error checking DB connection: %s", err)
			result["dbAccessible"] = false
		}
	}

	respdata, err := json.Marshal(result)
	if err != nil {
		log.Errorf("heartbeat failed to marshal JSON with error: %s", err)
		httpError(w, r, http.StatusInternalServerError, "error marshaling response JSON")
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	w.Write(respdata)
}

func handleVersion(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, r, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	dir, err := os.Getwd()
	if err != nil {
		httpError(w, r, http.StatusInternalServerError, "Could not get CWD")
		return
	}
	filename := path.Clean(dir + string(os.PathSeparator) + "version.json")
	f, err := os.Open(filename)
	if err != nil {
		httpError(w, r, http.StatusNotFound, "version.json file not found")
		return
	}
	stat, err := f.Stat()
	if err != nil {
		httpError(w, r, http.StatusInternalServerError, "stat failed on version.json")
		return
	}
	w.Header().Set("Content-Type", "application/json")
	http.ServeContent(w, r, "version.json", stat.ModTime(), f)
}
<commit_msg>handlers: add output hash to successful signing ops reqs<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package main

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"time"

	log "github.com/sirupsen/logrus"
	"go.mozilla.org/autograph/formats"
	"go.mozilla.org/autograph/signer"
)

// HeartbeatConfig configures the heartbeat handler. It sets timeouts
// for each backing service to check.
//
// `hsmHeartbeatSignerConf` is determined added on boot in initHSM
type heartbeatConfig struct {
	HSMCheckTimeout time.Duration
	DBCheckTimeout  time.Duration

	// hsmSignerConf is the signer conf to use to check
	// HSM connectivity (set to the first signer with an HSM label
	// in initHSM) when it is non-nil
	hsmSignerConf *signer.Configuration
}

// hashSHA256AsHex returns the hex encoded string of the SHA256 sum
// the arg toHash bytes
func hashSHA256AsHex(toHash []byte) string {
	h := sha256.New()
	h.Write(toHash)
	return fmt.Sprintf("%X", h.Sum(nil))
}

// handleSignature endpoint accepts a list of signature requests in a HAWK authenticated POST request
// and calls the signers to generate signature responses.
func (a *autographer) handleSignature(w http.ResponseWriter, r *http.Request) {
	rid := getRequestID(r)
	starttime := getRequestStartTime(r)
	auth, userid, err := a.authorizeHeader(r)
	if err != nil {
		if a.stats != nil {
			sendStatsErr := a.stats.Timing("hawk.authorize_header_failed", time.Since(starttime), nil, 1.0)
			if sendStatsErr != nil {
				log.Warnf("Error sending hawk.authorize_header_failed: %s", sendStatsErr)
			}
		}
		httpError(w, r, http.StatusUnauthorized, "authorization verification failed: %v", err)
		return
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		httpError(w, r, http.StatusBadRequest, "failed to read request body: %s", err)
		return
	}
	if r.Header.Get("Content-Type") != "application/json" {
		httpError(w, r, http.StatusBadRequest, "invalid content type, expected application/json")
		return
	}
	if len(body) < 10 {
		// it's impossible to have a valid request body smaller than 10 bytes
		httpError(w, r, http.StatusBadRequest, "empty or invalid request request body")
		return
	}
	if len(body) > 1048576000 {
		// the max body size is hardcoded to 1GB. Seriously, what are you trying to sign?
		httpError(w, r, http.StatusBadRequest, "request exceeds max size of 1GB")
		return
	}
	err = a.authorizeBody(auth, r, body)
	if a.stats != nil {
		sendStatsErr := a.stats.Timing("authorize_finished", time.Since(starttime), nil, 1.0)
		if sendStatsErr != nil {
			log.Warnf("Error sending authorize_finished: %s", sendStatsErr)
		}
	}
	if err != nil {
		httpError(w, r, http.StatusUnauthorized, "authorization verification failed: %v", err)
		return
	}
	var sigreqs []formats.SignatureRequest
	err = json.Unmarshal(body, &sigreqs)
	if a.stats != nil {
		sendStatsErr := a.stats.Timing("body_unmarshaled", time.Since(starttime), nil, 1.0)
		if sendStatsErr != nil {
			log.Warnf("Error sending body_unmarshaled: %s", sendStatsErr)
		}
	}
	if err != nil {
		httpError(w, r, http.StatusBadRequest, "failed to parse request body: %v", err)
		return
	}
	for i, sigreq := range sigreqs {
		if sigreq.Input == "" {
			httpError(w, r, http.StatusBadRequest, fmt.Sprintf("missing input in signature request %d", i))
		}
	}
	if a.debug {
		fmt.Printf("signature request\n-----------------\n%s\n", body)
	}
	sigresps := make([]formats.SignatureResponse, len(sigreqs))
	// Each signature requested in the http request body is processed individually.
	// For each, a signer is looked up, and used to compute a raw signature
	// the signature is then encoded appropriately, and added to the response slice
	for i, sigreq := range sigreqs {
		var (
			input                 []byte
			sig                   signer.Signature
			signedfile            []byte
			inputHash, outputHash string
		)

		// Decode the base64 input data
		input, err = base64.StdEncoding.DecodeString(sigreq.Input)
		if err != nil {
			httpError(w, r, http.StatusBadRequest, "%v", err)
			return
		}

		// Find the ID of the requested signer
		// Return an error if the signer is not found or if the user is not allowed
		// to use this signer
		signerID, err := a.getSignerID(userid, sigreq.KeyID)
		if err != nil || signerID < 0 {
			httpError(w, r, http.StatusUnauthorized, "%v", err)
			return
		}
		sigresps[i] = formats.SignatureResponse{
			Ref:        id(),
			Type:       a.signers[signerID].Config().Type,
			Mode:       a.signers[signerID].Config().Mode,
			SignerID:   a.signers[signerID].Config().ID,
			PublicKey:  a.signers[signerID].Config().PublicKey,
			SignedFile: base64.StdEncoding.EncodeToString(signedfile),
			X5U:        a.signers[signerID].Config().X5U,
			SignerOpts: a.signers[signerID].Config().SignerOpts,
		}
		// Make sure the signer implements the right interface, then sign the data
		switch r.URL.RequestURI() {
		case "/sign/hash":
			hashSigner, ok := a.signers[signerID].(signer.HashSigner)
			if !ok {
				httpError(w, r, http.StatusBadRequest, "requested signer does not implement hash signing")
				return
			}
			sig, err = hashSigner.SignHash(input, sigreq.Options)
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
				return
			}
			sigresps[i].Signature, err = sig.(signer.Signature).Marshal()
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "encoding failed with error: %v", err)
				return
			}
			// the input is already a hash just convert it to hex
			inputHash = fmt.Sprintf("%X", input)
			outputHash = "unimplemented"
		case "/sign/data":
			dataSigner, ok := a.signers[signerID].(signer.DataSigner)
			if !ok {
				httpError(w, r, http.StatusBadRequest, "requested signer does not implement data signing")
				return
			}
			sig, err = dataSigner.SignData(input, sigreq.Options)
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
				return
			}
			sigresps[i].Signature, err = sig.(signer.Signature).Marshal()
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "encoding failed with error: %v", err)
				return
			}
			// calculate a hash of the input to store in the signing logs
			inputHash = hashSHA256AsHex(input)
			outputHash = hashSHA256AsHex([]byte(sigresps[i].Signature))
		case "/sign/file":
			fileSigner, ok := a.signers[signerID].(signer.FileSigner)
			if !ok {
				httpError(w, r, http.StatusBadRequest, "requested signer does not implement file signing")
				return
			}
			signedfile, err = fileSigner.SignFile(input, sigreq.Options)
			if err != nil {
				httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
				return
			}
			sigresps[i].SignedFile = base64.StdEncoding.EncodeToString(signedfile)
			// calculate a hash of the input to store in the signing logs
			inputHash = hashSHA256AsHex(input)
			outputHash = hashSHA256AsHex(signedfile)
		}
		log.WithFields(log.Fields{
			"rid":         rid,
			"options":     sigreq.Options,
			"mode":        sigresps[i].Mode,
			"ref":         sigresps[i].Ref,
			"type":        sigresps[i].Type,
			"signer_id":   sigresps[i].SignerID,
			"input_hash":  inputHash,
			"output_hash": outputHash,
			"user_id":     userid,
			"t":           int32(time.Since(starttime) / time.Millisecond), //  request processing time in ms
		}).Info("signing operation succeeded")
	}
	respdata, err := json.Marshal(sigresps)
	if err != nil {
		httpError(w, r, http.StatusInternalServerError, "signing failed with error: %v", err)
		return
	}
	if a.debug {
		fmt.Printf("signature response\n------------------\n%s\n", respdata)
	}
	w.Header().Add("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	w.Write(respdata)
	log.WithFields(log.Fields{"rid": rid}).Info("signing request completed successfully")
}

// handleLBHeartbeat returns a simple message indicating that the API is alive and well
func handleLBHeartbeat(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, r, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	w.Write([]byte("ohai"))
}

// handleHeartbeat checks whether backing services are enabled and
// accessible and returns 200 when they are and 502 when the
// aren't. Currently it only checks whether the HSM is accessible.
func (a *autographer) handleHeartbeat(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, r, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	if a.heartbeatConf == nil {
		httpError(w, r, http.StatusInternalServerError, "Missing heartbeat config")
		return
	}
	var (
		// a map of backing service name to up or down/inaccessible status
		result         = map[string]bool{}
		status         = http.StatusOK
		requestContext = r.Context()
		rid            = getRequestID(r)
	)

	// try to fetch the private key from the HSM for the first
	// signer conf with a non-PEM private key that we saved on
	// server start
	if a.heartbeatConf.hsmSignerConf != nil {
		var (
			err                 error
			hsmSignerConf       = a.heartbeatConf.hsmSignerConf
			hsmHBTimeout        = a.heartbeatConf.HSMCheckTimeout
			checkResult         = make(chan error, 1)
			hsmHeartbeatStartTs = time.Now()
		)
		go func() {
			checkResult <- hsmSignerConf.CheckHSMConnection()
		}()
		select {
		case <-time.After(hsmHBTimeout):
			err = fmt.Errorf("Checking HSM connection for signer %s private key timed out", hsmSignerConf.ID)
		case err = <-checkResult:
		}

		if err == nil {
			log.WithFields(log.Fields{
				"rid":     rid,
				"t":       int32(time.Since(hsmHeartbeatStartTs) / time.Millisecond),
				"timeout": fmt.Sprintf("%s", hsmHBTimeout),
			}).Info("HSM heartbeat completed successfully")
			result["hsmAccessible"] = true
			status = http.StatusOK
		} else {
			log.Errorf("error checking HSM connection for signer %s: %s", hsmSignerConf.ID, err)
			result["hsmAccessible"] = false
			status = http.StatusInternalServerError
		}
	}

	// check the database connection and return its status, but
	// don't fail the heartbeat since we only care about DB
	// connectivity on server start
	if a.db != nil {
		dbHeartbeatStartTs := time.Now()
		dbCheckCtx, dbCancel := context.WithTimeout(requestContext, a.heartbeatConf.DBCheckTimeout)
		defer dbCancel()
		err := a.db.CheckConnectionContext(dbCheckCtx)
		if err == nil {
			log.WithFields(log.Fields{
				"rid":     rid,
				"t":       int32(time.Since(dbHeartbeatStartTs) / time.Millisecond),
				"timeout": fmt.Sprintf("%s", a.heartbeatConf.DBCheckTimeout),
			}).Info("DB heartbeat completed successfully")
			result["dbAccessible"] = true
		} else {
			log.Errorf("error checking DB connection: %s", err)
			result["dbAccessible"] = false
		}
	}

	respdata, err := json.Marshal(result)
	if err != nil {
		log.Errorf("heartbeat failed to marshal JSON with error: %s", err)
		httpError(w, r, http.StatusInternalServerError, "error marshaling response JSON")
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	w.Write(respdata)
}

func handleVersion(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		httpError(w, r, http.StatusMethodNotAllowed, "%s method not allowed; endpoint accepts GET only", r.Method)
		return
	}
	dir, err := os.Getwd()
	if err != nil {
		httpError(w, r, http.StatusInternalServerError, "Could not get CWD")
		return
	}
	filename := path.Clean(dir + string(os.PathSeparator) + "version.json")
	f, err := os.Open(filename)
	if err != nil {
		httpError(w, r, http.StatusNotFound, "version.json file not found")
		return
	}
	stat, err := f.Stat()
	if err != nil {
		httpError(w, r, http.StatusInternalServerError, "stat failed on version.json")
		return
	}
	w.Header().Set("Content-Type", "application/json")
	http.ServeContent(w, r, "version.json", stat.ModTime(), f)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"

	"github.com/carbocation/go.forum"
	"github.com/carbocation/go.user"
	"github.com/goods/httpbuf"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
)

type handler func(http.ResponseWriter, *http.Request) error

func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//Load session values into req
	OpenContext(req)

	//For now, print the user's info to the console all the time
	fmt.Printf("User object: %+v\n", context.Get(req, ThisUser))

	//Run the handler and grab the error, and report it. We buffer the
	// output so that handlers can modify session data at any point.
	buf := new(httpbuf.Buffer)
	if err := h(buf, req); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Save any changed session values
	CloseContext(req, buf)

	//apply the buffered response to the writer
	buf.Apply(w)
}

func loginHandler(w http.ResponseWriter, r *http.Request) (err error) {
	//execute the template
	data := struct {
		G    GlobalValues
		User *user.User
	}{
		globals,
		context.Get(r, ThisUser).(*user.User),
	}
	//T("login.html").Execute(w, map[string]interface{}{})
	T("login.html").Execute(w, data)
	return
}

func logoutHandler(w http.ResponseWriter, r *http.Request) (err error) {
	DeleteContext(r, w)

	http.Redirect(w, r, reverse("index"), http.StatusSeeOther)

	return
}

func indexHandler(w http.ResponseWriter, r *http.Request) (err error) {
	data := struct {
		G    GlobalValues
		User *user.User
	}{
		globals,
		context.Get(r, ThisUser).(*user.User),
	}

	T("index.html").Execute(w, data)

	return
}

func registerHandler(w http.ResponseWriter, r *http.Request) (err error) {
	data := struct {
		G        GlobalValues
		User     *user.User
		Messages []interface{}
	}{
		globals,
		context.Get(r, ThisUser).(*user.User),
		[]interface{}{},
	}

	//Don't let non-guests register again
	if !data.User.Guest() {
		http.Redirect(w, r, reverse("index"), http.StatusSeeOther)
	}

	session, _ := store.Get(r, "app")
	if flashes := session.Flashes(); len(flashes) > 0 {
		// Just print the flash values.
		data.Messages = flashes
	}

	T("register.html").Execute(w, data)
	return
}

func threadHandler(w http.ResponseWriter, r *http.Request) (err error) {
	//If the thread ID is not parseable as an integer, stop immediately
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		return errors.New("The requested thread is invalid.")
	}

	// Pull down the closuretable from the root requested id
	ct, err := forum.ClosureTable(id)
	if err != nil {
		return errors.New("The requested thread could not be found.")
	}

	entries, err := forum.DescendantEntries(id)
	if err != nil {
		return errors.New("The requested thread could not be found.")
	}

	//Make sure this not a forum
	if entries[id].Forum {
		http.Redirect(w, r, reverse("forum", "id", id), http.StatusSeeOther)
		return
	}

	//Obligatory boxing step
	interfaceEntries := map[int64]interface{}{}
	for k, v := range entries {
		interfaceEntries[k] = v
	}

	tree, err := ct.TableToTree(interfaceEntries)
	if err != nil {
		return errors.New("The requested data structure could not be built.")
	}

	data := map[string]interface{}{
		"G":    globals,
		"User": context.Get(r, ThisUser).(*user.User),
		"Tree": tree,
	}

	//execute the template
	T("thread.html").Execute(w, data)

	return
}

func forumHandler(w http.ResponseWriter, r *http.Request) (err error) {
	//If the forum ID is not parseable as an integer, stop immediately
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		return errors.New("The requested forum is invalid.")
	}

	// Pull down the closuretable from the root requested id
	ct, err := forum.DepthOneClosureTable(id)
	if err != nil {
		return errors.New("The requested thread could not be found.")
	}

	entries, err := forum.DepthOneDescendantEntries(id)
	if err != nil {
		return errors.New("The requested thread could not be found.")
	}

	//Make sure this is a forum
	if entries[id].Forum != true {
		http.Redirect(w, r, reverse("thread", "id", id), http.StatusSeeOther)
		return
	}

	//Obligatory boxing step
	interfaceEntries := map[int64]interface{}{}
	for k, v := range entries {
		interfaceEntries[k] = v
	}

	tree, err := ct.TableToTree(interfaceEntries)
	if err != nil {
		return errors.New("The requested data structure could not be built.")
	}

	data := map[string]interface{}{
		"G":    globals,
		"User": context.Get(r, ThisUser).(*user.User),
		"Tree": tree,
	}

	//execute the template
	T("forum.html").Execute(w, data)

	return
}

func newThreadHandler(w http.ResponseWriter, r *http.Request) (err error) {
	fmt.Fprint(w, "New thread form will go here.")

	return
}

func postLoginHandler(w http.ResponseWriter, r *http.Request) (err error) {
	r.ParseForm()

	login := new(user.User)
	//Parse the form values into the Login object
	decoder.Decode(login, r.Form)

	u, err := login.Login()
	if err != nil {
		u = new(user.User)
	}

	context.Set(r, ThisUser, u)

	//Add the user's struct to the session
	session, _ := store.Get(r, "app")
	session.Values["user"] = u

	//Redirect to a GET address to prevent form resubmission
	http.Redirect(w, r, reverse("index"), http.StatusSeeOther)

	return
}

func postRegisterHandler(w http.ResponseWriter, r *http.Request) (err error) {
	r.ParseForm()

	//Don't let non-guests register again
	if !context.Get(r, ThisUser).(*user.User).Guest() {
		http.Redirect(w, r, reverse("index"), http.StatusSeeOther)
		return
	}

	//Make sure the repeat passwords match
	if r.FormValue("PlaintextPassword") != r.FormValue("PlaintextPassword2") {
		http.Redirect(w, r, reverse("register"), http.StatusSeeOther)
		return
	}

	//Locate the session
	session, _ := store.Get(r, "app")

	//Try to create the new user in the database
	u := new(user.User)
	decoder.Decode(u, r.Form)
	err = u.Register()
	if err != nil {
		//If our registration fails for any reason, set a flag and show the form again
		//http.Redirect(w, r, reverse("register"), http.StatusSeeOther)
		context.Set(r, ThisUser, u)

		//Tell the user why we failed
		session.AddFlash(fmt.Sprintf("%s", err))

		return registerHandler(w, r)
	}

	//They're a real user. Overwrite full object by populating from the DB
	u, err = user.FindOneUserById(u.Id)
	context.Set(r, ThisUser, u)

	session.Values["user"] = u

	http.Redirect(w, r, reverse("index"), http.StatusSeeOther)

	return
}

func postThreadHandler(w http.ResponseWriter, r *http.Request) error {
	var pid int64 //parent ID
	var err error
	var parent, entry *forum.Entry

	entry = new(forum.Entry)
	r.ParseForm()
	decoder.Decode(entry, r.Form)

	fmt.Printf("%+v", entry)

	//Don't let guests post (currently)
	//TODO(james) automatically create accounts for guests who try to post
	if context.Get(r, ThisUser).(*user.User).Guest() {
		http.Error(w, "NowayBro!", http.StatusUnauthorized)

		return errors.New("Unauthorized")
	}

	//TODO(james) stop relying on the existence of a user ID here
	user := context.Get(r, ThisUser).(*user.User)
	entry.AuthorId = user.Id

	//Make sure the parent_id is valid
	if pid, err = strconv.ParseInt(r.FormValue("parent_id"), 10, 64); err != nil {
		return err
	}

	//Make sure the parent post exists
	if parent, err = forum.OneEntry(pid); err != nil {
		return err
	}

	//If posting to a forum, must have a title and body or URL.
	if parent.Forum {
		//Min length
		if len(entry.Body) < 5 {
			if u, err := url.Parse(entry.Url); err != nil {
				return errors.New("Either free-text or a URL must be provided.")
			} else {
				//The URL is valid
				entry.Url = u.String()
			}
		}
		//entry.Body is therefore valid
	} else {
		//Thread reply
		entry.Url = ""
		if len(entry.Body) < 5 {
			return errors.New("Please craft a longer reply.")
		}
		//entry.Body is therefore valid
	}

	err = entry.Persist(parent.Id)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Propagating more verbose errors.<commit_after>package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"

	"github.com/carbocation/go.forum"
	"github.com/carbocation/go.user"
	"github.com/goods/httpbuf"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
)

type handler func(http.ResponseWriter, *http.Request) error

func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	//Load session values into req
	OpenContext(req)

	//For now, print the user's info to the console all the time
	fmt.Printf("User object: %+v\n", context.Get(req, ThisUser))

	//Run the handler and grab the error, and report it. We buffer the
	// output so that handlers can modify session data at any point.
	buf := new(httpbuf.Buffer)
	if err := h(buf, req); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	//Save any changed session values
	CloseContext(req, buf)

	//apply the buffered response to the writer
	buf.Apply(w)
}

func loginHandler(w http.ResponseWriter, r *http.Request) (err error) {
	//execute the template
	data := struct {
		G    GlobalValues
		User *user.User
	}{
		globals,
		context.Get(r, ThisUser).(*user.User),
	}
	//T("login.html").Execute(w, map[string]interface{}{})
	T("login.html").Execute(w, data)
	return
}

func logoutHandler(w http.ResponseWriter, r *http.Request) (err error) {
	DeleteContext(r, w)

	http.Redirect(w, r, reverse("index"), http.StatusSeeOther)

	return
}

func indexHandler(w http.ResponseWriter, r *http.Request) (err error) {
	data := struct {
		G    GlobalValues
		User *user.User
	}{
		globals,
		context.Get(r, ThisUser).(*user.User),
	}

	T("index.html").Execute(w, data)

	return
}

func registerHandler(w http.ResponseWriter, r *http.Request) (err error) {
	data := struct {
		G        GlobalValues
		User     *user.User
		Messages []interface{}
	}{
		globals,
		context.Get(r, ThisUser).(*user.User),
		[]interface{}{},
	}

	//Don't let non-guests register again
	if !data.User.Guest() {
		http.Redirect(w, r, reverse("index"), http.StatusSeeOther)
	}

	session, _ := store.Get(r, "app")
	if flashes := session.Flashes(); len(flashes) > 0 {
		// Just print the flash values.
		data.Messages = flashes
	}

	T("register.html").Execute(w, data)
	return
}

func threadHandler(w http.ResponseWriter, r *http.Request) (err error) {
	//If the thread ID is not parseable as an integer, stop immediately
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		return errors.New("The requested thread is invalid.")
	}

	// Pull down the closuretable from the root requested id
	ct, err := forum.ClosureTable(id)
	if err != nil {
		fmt.Printf("main.threadHandler: %s", err)
		return errors.New("The requested thread's ancestry map could not be found.")
	}

	entries, err := forum.DescendantEntries(id)
	if err != nil {
		fmt.Printf("main.threadHandler: %s", err)
		return errors.New("The requested thread's neighbor entries could not be found.")
	}

	//Make sure this not a forum
	if entries[id].Forum {
		http.Redirect(w, r, reverse("forum", "id", id), http.StatusSeeOther)
		return
	}

	//Obligatory boxing step
	interfaceEntries := map[int64]interface{}{}
	for k, v := range entries {
		interfaceEntries[k] = v
	}

	tree, err := ct.TableToTree(interfaceEntries)
	if err != nil {
		fmt.Printf("main.threadHandler: Error converting closure table to tree: %s", err)
		return errors.New("The requested data structure could not be built.")
	}

	data := map[string]interface{}{
		"G":    globals,
		"User": context.Get(r, ThisUser).(*user.User),
		"Tree": tree,
	}

	//execute the template
	T("thread.html").Execute(w, data)

	return
}

func forumHandler(w http.ResponseWriter, r *http.Request) (err error) {
	//If the forum ID is not parseable as an integer, stop immediately
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		return errors.New("The requested forum is invalid.")
	}

	// Pull down the closuretable from the root requested id
	ct, err := forum.DepthOneClosureTable(id)
	fmt.Printf("%+v\n", ct)
	if err != nil {
		return errors.New("The requested forum's ancestry map could not be found.")
	}
	if ct.Size() < 1{
		return errors.New("The requested forum's ancestry map had 0 entries.")
	}

	entries, err := forum.DepthOneDescendantEntries(id)
	if err != nil {
		return errors.New("The requested forum's neighbor entries could not be found.")
	}

	//Make sure this is a forum
	if entries[id].Forum != true {
		http.Redirect(w, r, reverse("thread", "id", id), http.StatusSeeOther)
		return
	}

	//Obligatory boxing step
	interfaceEntries := map[int64]interface{}{}
	for k, v := range entries {
		interfaceEntries[k] = v
	}

	tree, err := ct.TableToTree(interfaceEntries)
	if err != nil {
		fmt.Printf("Error: %s", err)
		return errors.New("The requested data structure could not be built.")
	}

	data := map[string]interface{}{
		"G":    globals,
		"User": context.Get(r, ThisUser).(*user.User),
		"Tree": tree,
	}

	//execute the template
	T("forum.html").Execute(w, data)

	return
}

func newThreadHandler(w http.ResponseWriter, r *http.Request) (err error) {
	fmt.Fprint(w, "New thread form will go here.")

	return
}

func postLoginHandler(w http.ResponseWriter, r *http.Request) (err error) {
	r.ParseForm()

	login := new(user.User)
	//Parse the form values into the Login object
	decoder.Decode(login, r.Form)

	u, err := login.Login()
	if err != nil {
		u = new(user.User)
	}

	context.Set(r, ThisUser, u)

	//Add the user's struct to the session
	session, _ := store.Get(r, "app")
	session.Values["user"] = u

	//Redirect to a GET address to prevent form resubmission
	http.Redirect(w, r, reverse("index"), http.StatusSeeOther)

	return
}

func postRegisterHandler(w http.ResponseWriter, r *http.Request) (err error) {
	r.ParseForm()

	//Don't let non-guests register again
	if !context.Get(r, ThisUser).(*user.User).Guest() {
		http.Redirect(w, r, reverse("index"), http.StatusSeeOther)
		return
	}

	//Make sure the repeat passwords match
	if r.FormValue("PlaintextPassword") != r.FormValue("PlaintextPassword2") {
		http.Redirect(w, r, reverse("register"), http.StatusSeeOther)
		return
	}

	//Locate the session
	session, _ := store.Get(r, "app")

	//Try to create the new user in the database
	u := new(user.User)
	decoder.Decode(u, r.Form)
	err = u.Register()
	if err != nil {
		//If our registration fails for any reason, set a flag and show the form again
		//http.Redirect(w, r, reverse("register"), http.StatusSeeOther)
		context.Set(r, ThisUser, u)

		//Tell the user why we failed
		session.AddFlash(fmt.Sprintf("%s", err))

		return registerHandler(w, r)
	}

	//They're a real user. Overwrite full object by populating from the DB
	u, err = user.FindOneUserById(u.Id)
	context.Set(r, ThisUser, u)

	session.Values["user"] = u

	http.Redirect(w, r, reverse("index"), http.StatusSeeOther)

	return
}

func postThreadHandler(w http.ResponseWriter, r *http.Request) error {
	var pid int64 //parent ID
	var err error
	var parent, entry *forum.Entry

	entry = new(forum.Entry)
	r.ParseForm()
	decoder.Decode(entry, r.Form)

	fmt.Printf("%+v", entry)

	//Don't let guests post (currently)
	//TODO(james) automatically create accounts for guests who try to post
	if context.Get(r, ThisUser).(*user.User).Guest() {
		http.Error(w, "NowayBro!", http.StatusUnauthorized)

		return errors.New("Unauthorized")
	}

	//TODO(james) stop relying on the existence of a user ID here
	user := context.Get(r, ThisUser).(*user.User)
	entry.AuthorId = user.Id

	//Make sure the parent_id is valid
	if pid, err = strconv.ParseInt(r.FormValue("parent_id"), 10, 64); err != nil {
		return err
	}

	//Make sure the parent post exists
	if parent, err = forum.OneEntry(pid); err != nil {
		return err
	}

	//If posting to a forum, must have a title and body or URL.
	if parent.Forum {
		//Min length
		if len(entry.Body) < 5 {
			if u, err := url.Parse(entry.Url); err != nil {
				return errors.New("Either free-text or a URL must be provided.")
			} else {
				//The URL is valid
				entry.Url = u.String()
			}
		}
		//entry.Body is therefore valid
	} else {
		//Thread reply
		entry.Url = ""
		if len(entry.Body) < 5 {
			return errors.New("Please craft a longer reply.")
		}
		//entry.Body is therefore valid
	}

	err = entry.Persist(parent.Id)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/labstack/echo"
)

// StatusPostInput - JSON Param input for adding a new status check
type StatusPostInput struct {
	URL   string `json:"url"`
	Email string `json:"email"`
}

// SitesResponse - JSON response for listing sites
type SitesResponse struct {
	Sites []Site `json:"sites"`
}

// SiteResponse - JSON response for a single site
type SiteResponse struct {
	Key      string  `json:"key"`
	Statuses []int64 `json:"statuses"`
}

// GET /sites
//
// response:
//   sites: <array> sites registered with the system
func sitesHandler(c *echo.Context) error {
	return c.JSON(http.StatusOK, &SitesResponse{Sites: AllSites})
}

// POST /sites
//
// params:
//   url: <string, required> website url to check
//	 email: <string, options> email address to alert on status changes
//
// response:
//   site: <json> newly created site info
func createSiteHandler(c *echo.Context) error {
	var params StatusPostInput
	d := json.NewDecoder(c.Request().Body)
	err := d.Decode(&params)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "Unable to decode JSON body")
	}

	params.URL = strings.TrimSpace(params.URL)
	params.Email = strings.TrimSpace(params.Email)

	if 0 == len(strings.TrimSpace(params.URL)) {
		return c.JSON(http.StatusBadRequest, "URL parameter required")
	}

	newSite, err := redisClient.Sadd(redisScopedKey(sitesRedisKey), []byte(params.URL))
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "Problem saving your new site")
	}

	if len(params.Email) > 0 {
		_, err = redisClient.Hset(redisScopedKey(emailsRedisKey), params.URL, []byte(params.Email))
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, "Problem saving your site email address to alert")
		}
	}

	site := &Site{URL: params.URL}
	if newSite {
		go siteCheck(*site)
		AllSites = append(AllSites, *site)
	}
	return c.JSON(http.StatusCreated, site)
}

// GET /sites/:key
//
// response:
//	 site_key: <string> site hash key
//	 statuses: <array> chronologically ordered status codes from recent checks
func siteHandler(c *echo.Context) error {
	siteKey := c.P(0)
	redisKey := redisScopedKey(fmt.Sprintf("%s:%s", uptimeRedisKey, siteKey))

	rawStatuses, err := redisClient.Lrange(redisKey, 0, minutesInMonth)
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
	}

	statuses := make([]int64, len(rawStatuses))
	for i, s := range rawStatuses {
		statuses[i], err = strconv.ParseInt(string(s), 10, 16)
		if err != nil {
			statuses[i] = 0
		}
	}

	resp := SiteResponse{Statuses: statuses, Key: siteKey}

	return c.JSON(http.StatusOK, resp)
}
<commit_msg>Set site key on new site creation response<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/labstack/echo"
)

// StatusPostInput - JSON Param input for adding a new status check
type StatusPostInput struct {
	URL   string `json:"url"`
	Email string `json:"email"`
}

// SitesResponse - JSON response for listing sites
type SitesResponse struct {
	Sites []Site `json:"sites"`
}

// SiteResponse - JSON response for a single site
type SiteResponse struct {
	Key      string  `json:"key"`
	Statuses []int64 `json:"statuses"`
}

// GET /sites
//
// response:
//   sites: <array> sites registered with the system
func sitesHandler(c *echo.Context) error {
	return c.JSON(http.StatusOK, &SitesResponse{Sites: AllSites})
}

// POST /sites
//
// params:
//   url: <string, required> website url to check
//	 email: <string, options> email address to alert on status changes
//
// response:
//   site: <json> newly created site info
func createSiteHandler(c *echo.Context) error {
	var params StatusPostInput
	d := json.NewDecoder(c.Request().Body)
	err := d.Decode(&params)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "Unable to decode JSON body")
	}

	params.URL = strings.TrimSpace(params.URL)
	params.Email = strings.TrimSpace(params.Email)

	if 0 == len(strings.TrimSpace(params.URL)) {
		return c.JSON(http.StatusBadRequest, "URL parameter required")
	}

	newSite, err := redisClient.Sadd(redisScopedKey(sitesRedisKey), []byte(params.URL))
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, "Problem saving your new site")
	}

	if len(params.Email) > 0 {
		_, err = redisClient.Hset(redisScopedKey(emailsRedisKey), params.URL, []byte(params.Email))
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, "Problem saving your site email address to alert")
		}
	}

	site := &Site{URL: params.URL}
	site.Key = site.HashKey()
	if newSite {
		go siteCheck(*site)
		AllSites = append(AllSites, *site)
	}
	return c.JSON(http.StatusCreated, site)
}

// GET /sites/:key
//
// response:
//	 site_key: <string> site hash key
//	 statuses: <array> chronologically ordered status codes from recent checks
func siteHandler(c *echo.Context) error {
	siteKey := c.P(0)
	redisKey := redisScopedKey(fmt.Sprintf("%s:%s", uptimeRedisKey, siteKey))

	rawStatuses, err := redisClient.Lrange(redisKey, 0, minutesInMonth)
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
	}

	statuses := make([]int64, len(rawStatuses))
	for i, s := range rawStatuses {
		statuses[i], err = strconv.ParseInt(string(s), 10, 16)
		if err != nil {
			statuses[i] = 0
		}
	}

	resp := SiteResponse{Statuses: statuses, Key: siteKey}

	return c.JSON(http.StatusOK, resp)
}
<|endoftext|>