<commit_before>package core

import (
	"log"
	"net/http"

	"github.com/sprioc/composer/pkg/model"
	"github.com/sprioc/composer/pkg/rsp"
	"github.com/sprioc/composer/pkg/sql"
)

func GetUser(ref model.Ref) (model.User, rsp.Response) {
	log.Println(ref)
	if ref.Collection != model.Users {
		return model.User{}, rsp.Response{Message: "Ref is of the wrong collection type",
			Code: http.StatusBadRequest}
	}

	user, err := sql.GetUser(ref.Shortcode)
	if err != nil {
		switch err.Error() {
		case "User not found.":
			return model.User{}, rsp.Response{Message: err.Error(), Code: http.StatusNotFound}
		default:
			return model.User{}, rsp.Response{Message: err.Error(), Code: http.StatusInternalServerError}
		}
	}
	return user, rsp.Response{Code: http.StatusOK}
}

func GetImage(ref model.Ref) (model.Image, rsp.Response) {
	if ref.Collection != model.Images {
		return model.Image{}, rsp.Response{Message: "Ref is of the wrong collection type",
			Code: http.StatusBadRequest}
	}

	image, err := sql.GetImage(ref.Shortcode)
	if err != nil {
		return model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return image, rsp.Response{Code: http.StatusOK}
}
func GetUserImages(username string) ([]model.Image, rsp.Response) {
	images, err := sql.GetUserImages(username)
	if err != nil {
		return []model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return images, rsp.Response{Code: http.StatusOK}
}
func GetRecentImages(limit int) ([]model.Image, rsp.Response) {
	images, err := sql.GetRecentImages(limit)
	if err != nil {
		return []model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return images, rsp.Response{Code: http.StatusOK}
}
func GetFeaturedImages(limit int) ([]model.Image, rsp.Response) {
	images, err := sql.GetFeaturedImages(limit)
	if err != nil {
		return []model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return images, rsp.Response{Code: http.StatusOK}
}
<commit_msg>Cleaned up old logging calls<commit_after>package core

import (
	"net/http"

	"github.com/sprioc/composer/pkg/model"
	"github.com/sprioc/composer/pkg/rsp"
	"github.com/sprioc/composer/pkg/sql"
)

func GetUser(ref model.Ref) (model.User, rsp.Response) {
	if ref.Collection != model.Users {
		return model.User{}, rsp.Response{Message: "Ref is of the wrong collection type",
			Code: http.StatusBadRequest}
	}

	user, err := sql.GetUser(ref.Shortcode)
	if err != nil {
		switch err.Error() {
		case "User not found.":
			return model.User{}, rsp.Response{Message: err.Error(), Code: http.StatusNotFound}
		default:
			return model.User{}, rsp.Response{Message: err.Error(), Code: http.StatusInternalServerError}
		}
	}
	return user, rsp.Response{Code: http.StatusOK}
}

func GetImage(ref model.Ref) (model.Image, rsp.Response) {
	if ref.Collection != model.Images {
		return model.Image{}, rsp.Response{Message: "Ref is of the wrong collection type",
			Code: http.StatusBadRequest}
	}

	image, err := sql.GetImage(ref.Shortcode)
	if err != nil {
		return model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return image, rsp.Response{Code: http.StatusOK}
}
func GetUserImages(username string) ([]model.Image, rsp.Response) {
	images, err := sql.GetUserImages(username)
	if err != nil {
		return []model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return images, rsp.Response{Code: http.StatusOK}
}
func GetRecentImages(limit int) ([]model.Image, rsp.Response) {
	images, err := sql.GetRecentImages(limit)
	if err != nil {
		return []model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return images, rsp.Response{Code: http.StatusOK}
}
func GetFeaturedImages(limit int) ([]model.Image, rsp.Response) {
	images, err := sql.GetFeaturedImages(limit)
	if err != nil {
		return []model.Image{}, rsp.Response{Message: err.Error(),
			Code: http.StatusInternalServerError}
	}
	return images, rsp.Response{Code: http.StatusOK}
}
<|endoftext|>


<commit_before>// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gochimp

import (
	"encoding/json"
	"errors"
	"strconv"
	"time"
)

// see https://mandrillapp.com/api/docs/messages.html
const messages_send_endpoint string = "/messages/send.json"                   // Send a new transactional message through Mandrill
const messages_info_endpoint string = "/messages/info.json"                   // Get info about a message
const messages_send_template_endpoint string = "/messages/send-template.json" // Send a new transactional message through Mandrill using a template
const messages_search_endpoint string = "/messages/search.json"               // Search the content of recently sent messages and optionally narrow by date range, tags and senders
const messages_parse_endpoint string = "/messages/parse.json"                 // Parse the full MIME document for an email message, returning the content of the message broken into its constituent pieces
const messages_send_raw_endpoint string = "/messages/send-raw.json"           // Take a raw MIME document for a message, and send it exactly as if it were sent over the SMTP protocol
const messages_content_endpoint string = "/messages/content.json"

func (a *MandrillAPI) MessageContent(id string) (*MessageContent, error) {
	var response MessageContent
	var params map[string]interface{} = make(map[string]interface{})
	params["id"] = id
	err := parseMandrillJson(a, messages_content_endpoint, params, &response)
	return &response, err
}

func (a *MandrillAPI) MessageInfo(id string) (map[string]interface{}, error) {
	var response map[string]interface{}
	var params map[string]interface{} = make(map[string]interface{})
	params["id"] = id
	err := parseMandrillJson(a, messages_info_endpoint, params, &response)
	return response, err
}

//todo: add send_at, ip_pool and key to messagesend
func (a *MandrillAPI) MessageSend(message Message, async bool) ([]SendResponse, error) {
	var response []SendResponse
	var params map[string]interface{} = make(map[string]interface{})
	params["message"] = message
	params["async"] = async
	err := parseMandrillJson(a, messages_send_endpoint, params, &response)
	return response, err
}

func (a *MandrillAPI) MessageSendTemplate(templateName string, templateContent []Var, message Message, async bool) ([]SendResponse, error) {
	var response []SendResponse
	if templateName == "" {
		return response, errors.New("templateName cannot be blank")
	}
	var params map[string]interface{} = make(map[string]interface{})
	params["message"] = message
	params["template_name"] = templateName
	params["async"] = async
	params["template_content"] = templateContent
	err := parseMandrillJson(a, messages_send_template_endpoint, params, &response)
	return response, err
}

func (a *MandrillAPI) MessageSearch(searchRequest SearchRequest) ([]SearchResponse, error) {
	var response []SearchResponse
	var params map[string]interface{} = make(map[string]interface{})
	//todo remove this hack
	if searchRequest.Query != "" {
		params["query"] = searchRequest.Query
	}
	if !searchRequest.DateFrom.IsZero() {
		params["date_from"] = searchRequest.DateFrom
	}
	if !searchRequest.DateTo.IsZero() {
		params["date_to"] = searchRequest.DateTo
	}
	if len(searchRequest.Tags) > 0 {
		params["tags"] = searchRequest.Tags
	}
	if len(searchRequest.Senders) > 0 {
		params["senders"] = searchRequest.Senders
	}
	params["limit"] = searchRequest.Limit
	if len(searchRequest.APIKeys) > 0 {
		params["api_keys"] = searchRequest.APIKeys
	}
	err := parseMandrillJson(a, messages_search_endpoint, params, &response)
	return response, err
}

func (a *MandrillAPI) MessageParse(rawMessage string, async bool) (Message, error) {
	var response Message
	if rawMessage == "" {
		return response, errors.New("rawMessage cannot be blank")
	}
	var params map[string]interface{} = make(map[string]interface{})
	params["raw_message"] = rawMessage
	err := parseMandrillJson(a, messages_parse_endpoint, params, &response)
	return response, err
}

// Can return oneof Invalid_Key, ValidationError or GeneralError
func (a *MandrillAPI) MessageSendRaw(rawMessage string, to []string, from Recipient, async bool) ([]SendResponse, error) {
	var response []SendResponse
	if rawMessage == "" {
		return response, errors.New("rawMessage cannot be blank")
	}
	if len(to) <= 0 {
		return response, errors.New("You need at least one recipient in the To array")
	}

	var params map[string]interface{} = make(map[string]interface{})
	params["raw_message"] = rawMessage
	params["from_email"] = from.Email
	params["from_name"] = from.Name
	params["to"] = to
	params["async"] = async
	err := parseMandrillJson(a, messages_send_raw_endpoint, params, &response)
	return response, err
}

type TS struct {
	time.Time
}

func (t *TS) UnmarshalJSON(data []byte) error {
	i, err := strconv.ParseInt(string(data), 10, 64)
	if err != nil {
		return err
	}

	*t = TS{time.Unix(i, 0)}

	return nil
}

type SearchResponse struct {
	Timestamp    time.Duration       `json:"ts"`
	Id           string              `json:"_id"`
	Sender       string              `json:"sender"`
	Subject      string              `json:"subject"`
	Email        string              `json:"email"`
	Tags         []string            `json:"tags"`
	Opens        int                 `json:"opens"`
	Clicks       int                 `json:"clicks"`
	State        string              `json:"state"`
	Diag         string              `json:"diag"`
	Metadata     []map[string]string `json:"metadata"`
	Template     interface{}         `json:"template"`
	Resends      []Resend            `json:"resends"`
	SMTPEvents   []SMTPEvent         `json:"smtp_events"`
	OpensDetail  []ActivityDetail    `json:"opens_detail"`
	ClicksDetail []ActivityDetail    `json:"clicks_detail"`
}

type Resend struct {
	Timestamp time.Duration `json:"ts"`
}

type SMTPEvent struct {
	Timestamp     time.Duration `json:"ts"`
	Type          string        `json:"type"`
	Diagnostics   string        `json:"diag"`
	SourceIP      string        `json:"source_ip"`
	DestinationIP string        `json:"destination_ip"`
	Size          int           `json:"size"`
}

type ActivityDetail struct {
	Timestamp time.Duration `json:"ts"`
	IP        string        `json:"ip"`
	Url       string        `json:"url"`
	Location  string        `json:"location"`
	UserAgent string        `json:"ua"`
}

type SearchRequest struct {
	Query    string   `json:"query"`
	DateFrom APITime  `json:"date_from"`
	DateTo   APITime  `json:"date_to"`
	Tags     []string `json:"tags"`
	Senders  []string `json:"senders"`
	APIKeys  []string `json:"api_keys"`
	Limit    int      `json:"limit"`
}

type MessageContent struct {
	Timestamp TS        `json:"ts"`
	Id        string    `json:"_id"`
	FromEmail string    `json:"from_email"`
	FromName  string    `json:"from_name"`
	Subject   string    `json:"subject"`
	To        Recipient `json:"to"`
	Text      string    `json:"text"`
	Html      string    `json:"html"`
}

type Message struct {
	Html                    string              `json:"html,omitempty"`
	Text                    string              `json:"text,omitempty"`
	Subject                 string              `json:"subject"`
	FromEmail               string              `json:"from_email"`
	FromName                string              `json:"from_name"`
	To                      []Recipient         `json:"to"`
	Headers                 map[string]string   `json:"headers,omitempty"`
	Important               bool                `json:"important,omitempty"`
	TrackOpens              bool                `json:"track_opens"`
	TrackClicks             bool                `json:"track_clicks"`
	ViewContentLink         bool                `json:"view_content_link,omitempty"`
	AutoText                bool                `json:"auto_text,omitempty"`
	AutoHtml                bool                `json:"auto_html,omitempty"`
	UrlStripQS              bool                `json:"url_strip_qs,omitempty"`
	InlineCss               bool                `json:"inline_css,omitempty"`
	PreserveRecipients      bool                `json:"preserve_recipients,omitempty"`
	BCCAddress              string              `json:"bcc_address,omitempty"`
	TrackingDomain          string              `json:"tracking_domain,omitempty"`
	SigningDomain           string              `json:"signing_domain,omitempty"`
	ReturnPathDomain        string              `json:"return_path_domain,omitempty"`
	Merge                   bool                `json:"merge,omitempty"`
	GlobalMergeVars         []Var               `json:"global_merge_vars,omitempty"`
	MergeVars               []MergeVars         `json:"merge_vars,omitempty"`
	Tags                    []string            `json:"tags,omitempty"`
	Subaccount              string              `json:"subaccount,omitempty"`
	GoogleAnalyticsDomains  []string            `json:"google_analytics_domains,omitempty"`
	GoogleAnalyticsCampaign []string            `json:"google_analytics_campaign,omitempty"`
	Metadata                map[string]string   `json:"metadata,omitempty"`
	RecipientMetadata       []RecipientMetaData `json:"recipient_metadata,omitempty"`
	Attachments             []Attachment        `json:"attachments,omitempty"`
	MergeLanguage           string              `json:"merge_language,omitempty"`
	Images                  []Attachment        `json:"images,omitempty"`
}

func (m *Message) String() string {
	b, _ := json.Marshal(m)
	return string(b)
}

func (m *Message) AddHeader(key, value string) {
	if m.Headers == nil {
		m.Headers = make(map[string]string)
	}
	m.Headers[key] = value
}

func (m *Message) AddRecipients(r ...Recipient) {
	m.To = append(m.To, r...)
}

func (m *Message) AddGlobalMergeVar(globalvars ...Var) {
	m.GlobalMergeVars = append(m.GlobalMergeVars, globalvars...)
}

func (m *Message) AddMergeVar(vars ...MergeVars) {
	m.MergeVars = append(m.MergeVars, vars...)
}

func (m *Message) AddTag(tags ...string) {
	m.Tags = append(m.Tags, tags...)
}

func (m *Message) AddGoogleAnalyticsDomains(domains ...string) {
	m.GoogleAnalyticsDomains = append(m.GoogleAnalyticsDomains, domains...)
}

func (m *Message) AddGoogleAnalyticsCampaign(campaigns ...string) {
	m.GoogleAnalyticsCampaign = append(m.GoogleAnalyticsCampaign, campaigns...)
}

func (m *Message) AddMetadata(key, value string) {
	if m.Metadata == nil {
		m.Metadata = make(map[string]string)
	}
	m.Metadata[key] = value
}

func (m *Message) AddRecipientMetadata(metadata ...RecipientMetaData) {
	m.RecipientMetadata = append(m.RecipientMetadata, metadata...)
}

func (m *Message) AddAttachments(attachement ...Attachment) {
	m.Attachments = append(m.Attachments, attachement...)
}

func (m *Message) AddImages(image ...Attachment) {
	m.Images = append(m.Images, image...)
}

type Attachment struct {
	Type    string `json:"type"`
	Name    string `json:"name"`
	Content string `json:"content"`
}
type RecipientMetaData struct {
	Recipient string            `json:"rcpt"`
	Vars      map[string]string `json:"values"`
}

type MergeVars struct {
	Recipient string `json:"rcpt"`
	Vars      []Var  `json:"vars"`
}

type Var struct {
	Name    string      `json:"name"`
	Content interface{} `json:"content"`
}

func NewVar(name string, content interface{}) *Var {
	return &Var{Name: name, Content: content}
}

type Recipient struct {
	Email string `json:"email"`
	Name  string `json:"name"`
	Type  string `json:"type"`
}

type SendResponse struct {
	Email          string `json:"email"`
	Status         string `json:"status"`
	Id             string `json:"_id"`
	RejectedReason string `json:"reject_reason"`
}
<commit_msg>Fix type of SearchResponse Metadata<commit_after>// Copyright 2013 Matthew Baird
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gochimp

import (
	"encoding/json"
	"errors"
	"strconv"
	"time"
)

// see https://mandrillapp.com/api/docs/messages.html
const messages_send_endpoint string = "/messages/send.json"                   // Send a new transactional message through Mandrill
const messages_info_endpoint string = "/messages/info.json"                   // Get info about a message
const messages_send_template_endpoint string = "/messages/send-template.json" // Send a new transactional message through Mandrill using a template
const messages_search_endpoint string = "/messages/search.json"               // Search the content of recently sent messages and optionally narrow by date range, tags and senders
const messages_parse_endpoint string = "/messages/parse.json"                 // Parse the full MIME document for an email message, returning the content of the message broken into its constituent pieces
const messages_send_raw_endpoint string = "/messages/send-raw.json"           // Take a raw MIME document for a message, and send it exactly as if it were sent over the SMTP protocol
const messages_content_endpoint string = "/messages/content.json"

func (a *MandrillAPI) MessageContent(id string) (*MessageContent, error) {
	var response MessageContent
	var params map[string]interface{} = make(map[string]interface{})
	params["id"] = id
	err := parseMandrillJson(a, messages_content_endpoint, params, &response)
	return &response, err
}

func (a *MandrillAPI) MessageInfo(id string) (map[string]interface{}, error) {
	var response map[string]interface{}
	var params map[string]interface{} = make(map[string]interface{})
	params["id"] = id
	err := parseMandrillJson(a, messages_info_endpoint, params, &response)
	return response, err
}

//todo: add send_at, ip_pool and key to messagesend
func (a *MandrillAPI) MessageSend(message Message, async bool) ([]SendResponse, error) {
	var response []SendResponse
	var params map[string]interface{} = make(map[string]interface{})
	params["message"] = message
	params["async"] = async
	err := parseMandrillJson(a, messages_send_endpoint, params, &response)
	return response, err
}

func (a *MandrillAPI) MessageSendTemplate(templateName string, templateContent []Var, message Message, async bool) ([]SendResponse, error) {
	var response []SendResponse
	if templateName == "" {
		return response, errors.New("templateName cannot be blank")
	}
	var params map[string]interface{} = make(map[string]interface{})
	params["message"] = message
	params["template_name"] = templateName
	params["async"] = async
	params["template_content"] = templateContent
	err := parseMandrillJson(a, messages_send_template_endpoint, params, &response)
	return response, err
}

func (a *MandrillAPI) MessageSearch(searchRequest SearchRequest) ([]SearchResponse, error) {
	var response []SearchResponse
	var params map[string]interface{} = make(map[string]interface{})
	//todo remove this hack
	if searchRequest.Query != "" {
		params["query"] = searchRequest.Query
	}
	if !searchRequest.DateFrom.IsZero() {
		params["date_from"] = searchRequest.DateFrom
	}
	if !searchRequest.DateTo.IsZero() {
		params["date_to"] = searchRequest.DateTo
	}
	if len(searchRequest.Tags) > 0 {
		params["tags"] = searchRequest.Tags
	}
	if len(searchRequest.Senders) > 0 {
		params["senders"] = searchRequest.Senders
	}
	params["limit"] = searchRequest.Limit
	if len(searchRequest.APIKeys) > 0 {
		params["api_keys"] = searchRequest.APIKeys
	}
	err := parseMandrillJson(a, messages_search_endpoint, params, &response)
	return response, err
}

func (a *MandrillAPI) MessageParse(rawMessage string, async bool) (Message, error) {
	var response Message
	if rawMessage == "" {
		return response, errors.New("rawMessage cannot be blank")
	}
	var params map[string]interface{} = make(map[string]interface{})
	params["raw_message"] = rawMessage
	err := parseMandrillJson(a, messages_parse_endpoint, params, &response)
	return response, err
}

// Can return oneof Invalid_Key, ValidationError or GeneralError
func (a *MandrillAPI) MessageSendRaw(rawMessage string, to []string, from Recipient, async bool) ([]SendResponse, error) {
	var response []SendResponse
	if rawMessage == "" {
		return response, errors.New("rawMessage cannot be blank")
	}
	if len(to) <= 0 {
		return response, errors.New("You need at least one recipient in the To array")
	}

	var params map[string]interface{} = make(map[string]interface{})
	params["raw_message"] = rawMessage
	params["from_email"] = from.Email
	params["from_name"] = from.Name
	params["to"] = to
	params["async"] = async
	err := parseMandrillJson(a, messages_send_raw_endpoint, params, &response)
	return response, err
}

type TS struct {
	time.Time
}

func (t *TS) UnmarshalJSON(data []byte) error {
	i, err := strconv.ParseInt(string(data), 10, 64)
	if err != nil {
		return err
	}

	*t = TS{time.Unix(i, 0)}

	return nil
}

type SearchResponse struct {
	Timestamp    time.Duration     `json:"ts"`
	Id           string            `json:"_id"`
	Sender       string            `json:"sender"`
	Subject      string            `json:"subject"`
	Email        string            `json:"email"`
	Tags         []string          `json:"tags"`
	Opens        int               `json:"opens"`
	Clicks       int               `json:"clicks"`
	State        string            `json:"state"`
	Diag         string            `json:"diag"`
	Metadata     map[string]string `json:"metadata"`
	Template     interface{}       `json:"template"`
	Resends      []Resend          `json:"resends"`
	SMTPEvents   []SMTPEvent       `json:"smtp_events"`
	OpensDetail  []ActivityDetail  `json:"opens_detail"`
	ClicksDetail []ActivityDetail  `json:"clicks_detail"`
}

type Resend struct {
	Timestamp time.Duration `json:"ts"`
}

type SMTPEvent struct {
	Timestamp     time.Duration `json:"ts"`
	Type          string        `json:"type"`
	Diagnostics   string        `json:"diag"`
	SourceIP      string        `json:"source_ip"`
	DestinationIP string        `json:"destination_ip"`
	Size          int           `json:"size"`
}

type ActivityDetail struct {
	Timestamp time.Duration `json:"ts"`
	IP        string        `json:"ip"`
	Url       string        `json:"url"`
	Location  string        `json:"location"`
	UserAgent string        `json:"ua"`
}

type SearchRequest struct {
	Query    string   `json:"query"`
	DateFrom APITime  `json:"date_from"`
	DateTo   APITime  `json:"date_to"`
	Tags     []string `json:"tags"`
	Senders  []string `json:"senders"`
	APIKeys  []string `json:"api_keys"`
	Limit    int      `json:"limit"`
}

type MessageContent struct {
	Timestamp TS        `json:"ts"`
	Id        string    `json:"_id"`
	FromEmail string    `json:"from_email"`
	FromName  string    `json:"from_name"`
	Subject   string    `json:"subject"`
	To        Recipient `json:"to"`
	Text      string    `json:"text"`
	Html      string    `json:"html"`
}

type Message struct {
	Html                    string              `json:"html,omitempty"`
	Text                    string              `json:"text,omitempty"`
	Subject                 string              `json:"subject"`
	FromEmail               string              `json:"from_email"`
	FromName                string              `json:"from_name"`
	To                      []Recipient         `json:"to"`
	Headers                 map[string]string   `json:"headers,omitempty"`
	Important               bool                `json:"important,omitempty"`
	TrackOpens              bool                `json:"track_opens"`
	TrackClicks             bool                `json:"track_clicks"`
	ViewContentLink         bool                `json:"view_content_link,omitempty"`
	AutoText                bool                `json:"auto_text,omitempty"`
	AutoHtml                bool                `json:"auto_html,omitempty"`
	UrlStripQS              bool                `json:"url_strip_qs,omitempty"`
	InlineCss               bool                `json:"inline_css,omitempty"`
	PreserveRecipients      bool                `json:"preserve_recipients,omitempty"`
	BCCAddress              string              `json:"bcc_address,omitempty"`
	TrackingDomain          string              `json:"tracking_domain,omitempty"`
	SigningDomain           string              `json:"signing_domain,omitempty"`
	ReturnPathDomain        string              `json:"return_path_domain,omitempty"`
	Merge                   bool                `json:"merge,omitempty"`
	GlobalMergeVars         []Var               `json:"global_merge_vars,omitempty"`
	MergeVars               []MergeVars         `json:"merge_vars,omitempty"`
	Tags                    []string            `json:"tags,omitempty"`
	Subaccount              string              `json:"subaccount,omitempty"`
	GoogleAnalyticsDomains  []string            `json:"google_analytics_domains,omitempty"`
	GoogleAnalyticsCampaign []string            `json:"google_analytics_campaign,omitempty"`
	Metadata                map[string]string   `json:"metadata,omitempty"`
	RecipientMetadata       []RecipientMetaData `json:"recipient_metadata,omitempty"`
	Attachments             []Attachment        `json:"attachments,omitempty"`
	MergeLanguage           string              `json:"merge_language,omitempty"`
	Images                  []Attachment        `json:"images,omitempty"`
}

func (m *Message) String() string {
	b, _ := json.Marshal(m)
	return string(b)
}

func (m *Message) AddHeader(key, value string) {
	if m.Headers == nil {
		m.Headers = make(map[string]string)
	}
	m.Headers[key] = value
}

func (m *Message) AddRecipients(r ...Recipient) {
	m.To = append(m.To, r...)
}

func (m *Message) AddGlobalMergeVar(globalvars ...Var) {
	m.GlobalMergeVars = append(m.GlobalMergeVars, globalvars...)
}

func (m *Message) AddMergeVar(vars ...MergeVars) {
	m.MergeVars = append(m.MergeVars, vars...)
}

func (m *Message) AddTag(tags ...string) {
	m.Tags = append(m.Tags, tags...)
}

func (m *Message) AddGoogleAnalyticsDomains(domains ...string) {
	m.GoogleAnalyticsDomains = append(m.GoogleAnalyticsDomains, domains...)
}

func (m *Message) AddGoogleAnalyticsCampaign(campaigns ...string) {
	m.GoogleAnalyticsCampaign = append(m.GoogleAnalyticsCampaign, campaigns...)
}

func (m *Message) AddMetadata(key, value string) {
	if m.Metadata == nil {
		m.Metadata = make(map[string]string)
	}
	m.Metadata[key] = value
}

func (m *Message) AddRecipientMetadata(metadata ...RecipientMetaData) {
	m.RecipientMetadata = append(m.RecipientMetadata, metadata...)
}

func (m *Message) AddAttachments(attachement ...Attachment) {
	m.Attachments = append(m.Attachments, attachement...)
}

func (m *Message) AddImages(image ...Attachment) {
	m.Images = append(m.Images, image...)
}

type Attachment struct {
	Type    string `json:"type"`
	Name    string `json:"name"`
	Content string `json:"content"`
}
type RecipientMetaData struct {
	Recipient string            `json:"rcpt"`
	Vars      map[string]string `json:"values"`
}

type MergeVars struct {
	Recipient string `json:"rcpt"`
	Vars      []Var  `json:"vars"`
}

type Var struct {
	Name    string      `json:"name"`
	Content interface{} `json:"content"`
}

func NewVar(name string, content interface{}) *Var {
	return &Var{Name: name, Content: content}
}

type Recipient struct {
	Email string `json:"email"`
	Name  string `json:"name"`
	Type  string `json:"type"`
}

type SendResponse struct {
	Email          string `json:"email"`
	Status         string `json:"status"`
	Id             string `json:"_id"`
	RejectedReason string `json:"reject_reason"`
}
<|endoftext|>


<commit_before>package pack

import (
	"crypto/sha256"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"sync"

	"github.com/restic/restic/backend"
	"github.com/restic/restic/crypto"
)

type BlobType uint8

const (
	Data BlobType = 0
	Tree          = 1
)

func (t BlobType) String() string {
	switch t {
	case Data:
		return "data"
	case Tree:
		return "tree"
	}

	return fmt.Sprintf("<BlobType %d>", t)
}

func (t BlobType) MarshalJSON() ([]byte, error) {
	switch t {
	case Data:
		return []byte(`"data"`), nil
	case Tree:
		return []byte(`"tree"`), nil
	}

	return nil, errors.New("unknown blob type")
}

func (t *BlobType) UnmarshalJSON(buf []byte) error {
	switch string(buf) {
	case `"data"`:
		*t = Data
	case `"tree"`:
		*t = Tree
	default:
		return errors.New("unknown blob type")
	}

	return nil
}

// Blob is a blob within a pack.
type Blob struct {
	Type   BlobType
	Length uint32
	ID     backend.ID
	Offset uint
}

// GetReader returns an io.Reader for the blob entry e.
func (e Blob) GetReader(rd io.ReadSeeker) (io.Reader, error) {
	// seek to the correct location
	_, err := rd.Seek(int64(e.Offset), 0)
	if err != nil {
		return nil, err
	}

	return io.LimitReader(rd, int64(e.Length)), nil
}

// Packer is used to create a new Pack.
type Packer struct {
	blobs []Blob

	bytes uint
	k     *crypto.Key
	wr    io.Writer
	hw    *backend.HashingWriter

	m sync.Mutex
}

// NewPacker returns a new Packer that can be used to pack blobs
// together.
func NewPacker(k *crypto.Key, w io.Writer) *Packer {
	return &Packer{k: k, wr: w, hw: backend.NewHashingWriter(w, sha256.New())}
}

// Add saves the data read from rd as a new blob to the packer. Returned is the
// number of bytes written to the pack.
func (p *Packer) Add(t BlobType, id backend.ID, rd io.Reader) (int64, error) {
	p.m.Lock()
	defer p.m.Unlock()

	c := Blob{Type: t, ID: id}

	n, err := io.Copy(p.hw, rd)
	c.Length = uint32(n)
	c.Offset = p.bytes
	p.bytes += uint(n)
	p.blobs = append(p.blobs, c)

	return n, err
}

var entrySize = uint(binary.Size(BlobType(0)) + binary.Size(uint32(0)) + backend.IDSize)

// headerEntry is used with encoding/binary to read and write header entries
type headerEntry struct {
	Type   BlobType
	Length uint32
	ID     [backend.IDSize]byte
}

// Finalize writes the header for all added blobs and finalizes the pack.
// Returned are the complete number of bytes written, including the header.
// After Finalize() has finished, the ID of this pack can be obtained by
// calling ID().
func (p *Packer) Finalize() (bytesWritten uint, err error) {
	p.m.Lock()
	defer p.m.Unlock()

	bytesWritten = p.bytes

	// create writer to encrypt header
	wr := crypto.EncryptTo(p.k, p.hw)

	// write header
	for _, b := range p.blobs {
		entry := headerEntry{
			Type:   b.Type,
			Length: b.Length,
		}
		copy(entry.ID[:], b.ID)

		err := binary.Write(wr, binary.LittleEndian, entry)
		if err != nil {
			wr.Close()
			return bytesWritten, err
		}

		bytesWritten += entrySize
	}

	// finalize encrypted header
	err = wr.Close()
	if err != nil {
		return bytesWritten, err
	}

	// account for crypto overhead
	bytesWritten += crypto.Extension

	// write length
	err = binary.Write(p.hw, binary.LittleEndian, uint32(uint(len(p.blobs))*entrySize+crypto.Extension))
	if err != nil {
		return bytesWritten, err
	}
	bytesWritten += uint(binary.Size(uint32(0)))

	p.bytes = uint(bytesWritten)

	return bytesWritten, nil
}

// ID returns the ID of all data written so far.
func (p *Packer) ID() backend.ID {
	p.m.Lock()
	defer p.m.Unlock()

	return p.hw.Sum(nil)
}

// Size returns the number of bytes written so far.
func (p *Packer) Size() uint {
	p.m.Lock()
	defer p.m.Unlock()

	return p.bytes
}

// Count returns the number of blobs in this packer.
func (p *Packer) Count() int {
	p.m.Lock()
	defer p.m.Unlock()

	return len(p.blobs)
}

// Blobs returns the slice of blobs that have been written.
func (p *Packer) Blobs() []Blob {
	p.m.Lock()
	defer p.m.Unlock()

	return p.blobs
}

// Writer returns the underlying writer.
func (p *Packer) Writer() io.Writer {
	return p.wr
}

func (p *Packer) String() string {
	return fmt.Sprintf("<Packer %d blobs, %d bytes>", len(p.blobs), p.bytes)
}

// Unpacker is used to read individual blobs from a pack.
type Unpacker struct {
	rd      io.ReadSeeker
	Entries []Blob
	k       *crypto.Key
}

// NewUnpacker returns a pointer to Unpacker which can be used to read
// individual Blobs from a pack.
func NewUnpacker(k *crypto.Key, entries []Blob, rd io.ReadSeeker) (*Unpacker, error) {
	var err error
	ls := binary.Size(uint32(0))

	// reset to the end to read header length
	_, err = rd.Seek(-int64(ls), 2)
	if err != nil {
		return nil, fmt.Errorf("seeking to read header length failed: %v", err)
	}

	var length uint32
	err = binary.Read(rd, binary.LittleEndian, &length)
	if err != nil {
		return nil, fmt.Errorf("reading header length failed: %v", err)
	}

	// reset to the beginning of the header
	_, err = rd.Seek(-int64(ls)-int64(length), 2)
	if err != nil {
		return nil, fmt.Errorf("seeking to read header length failed: %v", err)
	}

	// read header
	hrd, err := crypto.DecryptFrom(k, io.LimitReader(rd, int64(length)))
	if err != nil {
		return nil, err
	}

	if entries == nil {
		pos := uint(0)
		for {
			e := headerEntry{}
			err = binary.Read(hrd, binary.LittleEndian, &e)
			if err == io.EOF {
				break
			}

			if err != nil {
				return nil, err
			}

			entries = append(entries, Blob{
				Type:   e.Type,
				Length: e.Length,
				ID:     e.ID[:],
				Offset: pos,
			})

			pos += uint(e.Length)
		}
	}

	p := &Unpacker{
		rd:      rd,
		k:       k,
		Entries: entries,
	}

	return p, nil
}
<commit_msg>pack: Refactor Finalize()<commit_after>package pack

import (
	"crypto/sha256"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"sync"

	"github.com/restic/restic/backend"
	"github.com/restic/restic/crypto"
)

type BlobType uint8

const (
	Data BlobType = 0
	Tree          = 1
)

func (t BlobType) String() string {
	switch t {
	case Data:
		return "data"
	case Tree:
		return "tree"
	}

	return fmt.Sprintf("<BlobType %d>", t)
}

func (t BlobType) MarshalJSON() ([]byte, error) {
	switch t {
	case Data:
		return []byte(`"data"`), nil
	case Tree:
		return []byte(`"tree"`), nil
	}

	return nil, errors.New("unknown blob type")
}

func (t *BlobType) UnmarshalJSON(buf []byte) error {
	switch string(buf) {
	case `"data"`:
		*t = Data
	case `"tree"`:
		*t = Tree
	default:
		return errors.New("unknown blob type")
	}

	return nil
}

// Blob is a blob within a pack.
type Blob struct {
	Type   BlobType
	Length uint32
	ID     backend.ID
	Offset uint
}

// GetReader returns an io.Reader for the blob entry e.
func (e Blob) GetReader(rd io.ReadSeeker) (io.Reader, error) {
	// seek to the correct location
	_, err := rd.Seek(int64(e.Offset), 0)
	if err != nil {
		return nil, err
	}

	return io.LimitReader(rd, int64(e.Length)), nil
}

// Packer is used to create a new Pack.
type Packer struct {
	blobs []Blob

	bytes uint
	k     *crypto.Key
	wr    io.Writer
	hw    *backend.HashingWriter

	m sync.Mutex
}

// NewPacker returns a new Packer that can be used to pack blobs
// together.
func NewPacker(k *crypto.Key, w io.Writer) *Packer {
	return &Packer{k: k, wr: w, hw: backend.NewHashingWriter(w, sha256.New())}
}

// Add saves the data read from rd as a new blob to the packer. Returned is the
// number of bytes written to the pack.
func (p *Packer) Add(t BlobType, id backend.ID, rd io.Reader) (int64, error) {
	p.m.Lock()
	defer p.m.Unlock()

	c := Blob{Type: t, ID: id}

	n, err := io.Copy(p.hw, rd)
	c.Length = uint32(n)
	c.Offset = p.bytes
	p.bytes += uint(n)
	p.blobs = append(p.blobs, c)

	return n, err
}

var entrySize = uint(binary.Size(BlobType(0)) + binary.Size(uint32(0)) + backend.IDSize)

// headerEntry is used with encoding/binary to read and write header entries
type headerEntry struct {
	Type   BlobType
	Length uint32
	ID     [backend.IDSize]byte
}

// Finalize writes the header for all added blobs and finalizes the pack.
// Returned are the complete number of bytes written, including the header.
// After Finalize() has finished, the ID of this pack can be obtained by
// calling ID().
func (p *Packer) Finalize() (bytesWritten uint, err error) {
	p.m.Lock()
	defer p.m.Unlock()

	bytesWritten = p.bytes

	// create writer to encrypt header
	wr := crypto.EncryptTo(p.k, p.hw)

	bytesHeader, err := p.writeHeader(wr)
	if err != nil {
		wr.Close()
		return bytesWritten + bytesHeader, err
	}

	// finalize encrypted header
	err = wr.Close()
	if err != nil {
		return bytesWritten, err
	}

	// account for crypto overhead
	bytesWritten += crypto.Extension

	// write length
	err = binary.Write(p.hw, binary.LittleEndian, uint32(uint(len(p.blobs))*entrySize+crypto.Extension))
	if err != nil {
		return bytesWritten, err
	}
	bytesWritten += uint(binary.Size(uint32(0)))

	p.bytes = uint(bytesWritten)

	return bytesWritten, nil
}

// writeHeader constructs and writes the header to wr.
func (p *Packer) writeHeader(wr io.Writer) (bytesWritten uint, err error) {
	for _, b := range p.blobs {
		entry := headerEntry{
			Type:   b.Type,
			Length: b.Length,
		}
		copy(entry.ID[:], b.ID)

		err := binary.Write(wr, binary.LittleEndian, entry)
		if err != nil {
			return bytesWritten, err
		}

		bytesWritten += entrySize
	}

	return
}

// ID returns the ID of all data written so far.
func (p *Packer) ID() backend.ID {
	p.m.Lock()
	defer p.m.Unlock()

	return p.hw.Sum(nil)
}

// Size returns the number of bytes written so far.
func (p *Packer) Size() uint {
	p.m.Lock()
	defer p.m.Unlock()

	return p.bytes
}

// Count returns the number of blobs in this packer.
func (p *Packer) Count() int {
	p.m.Lock()
	defer p.m.Unlock()

	return len(p.blobs)
}

// Blobs returns the slice of blobs that have been written.
func (p *Packer) Blobs() []Blob {
	p.m.Lock()
	defer p.m.Unlock()

	return p.blobs
}

// Writer returns the underlying writer.
func (p *Packer) Writer() io.Writer {
	return p.wr
}

func (p *Packer) String() string {
	return fmt.Sprintf("<Packer %d blobs, %d bytes>", len(p.blobs), p.bytes)
}

// Unpacker is used to read individual blobs from a pack.
type Unpacker struct {
	rd      io.ReadSeeker
	Entries []Blob
	k       *crypto.Key
}

// NewUnpacker returns a pointer to Unpacker which can be used to read
// individual Blobs from a pack.
func NewUnpacker(k *crypto.Key, entries []Blob, rd io.ReadSeeker) (*Unpacker, error) {
	var err error
	ls := binary.Size(uint32(0))

	// reset to the end to read header length
	_, err = rd.Seek(-int64(ls), 2)
	if err != nil {
		return nil, fmt.Errorf("seeking to read header length failed: %v", err)
	}

	var length uint32
	err = binary.Read(rd, binary.LittleEndian, &length)
	if err != nil {
		return nil, fmt.Errorf("reading header length failed: %v", err)
	}

	// reset to the beginning of the header
	_, err = rd.Seek(-int64(ls)-int64(length), 2)
	if err != nil {
		return nil, fmt.Errorf("seeking to read header length failed: %v", err)
	}

	// read header
	hrd, err := crypto.DecryptFrom(k, io.LimitReader(rd, int64(length)))
	if err != nil {
		return nil, err
	}

	if entries == nil {
		pos := uint(0)
		for {
			e := headerEntry{}
			err = binary.Read(hrd, binary.LittleEndian, &e)
			if err == io.EOF {
				break
			}

			if err != nil {
				return nil, err
			}

			entries = append(entries, Blob{
				Type:   e.Type,
				Length: e.Length,
				ID:     e.ID[:],
				Offset: pos,
			})

			pos += uint(e.Length)
		}
	}

	p := &Unpacker{
		rd:      rd,
		k:       k,
		Entries: entries,
	}

	return p, nil
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
)

type Server struct {
}

func New() *Server {
	return &Server{}
}

func (s *Server) Start(host string) {
	fmt.Println("Hello server")
}
<commit_msg>Add super simple http and websocket server handlers<commit_after>package server

import (
    "net/http"
    "github.com/gorilla/websocket"
    "log"
    "fmt"
)

type Server struct {
    upgrader        websocket.Upgrader
    connections     []*websocket.Conn
}

func webSocketHandler(conn *websocket.Conn) {
    log.Println("New websocket connection available")
    for {
        msgType, msg, err := conn.ReadMessage()
        if err != nil {
            log.Println("Connection read error", err.Error())
            return
        }

        if msgType == websocket.TextMessage {
            log.Println(string(msg))
        }

        if err = conn.WriteMessage(msgType, msg); err != nil {
            log.Println("Connection write error", err.Error())
            return
        }
    }
}

func New() *Server {
	return &Server{
    }
}

func (s *Server) Start(host string) {

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World")
    })

    var upgrader = websocket.Upgrader{
        ReadBufferSize: 1024,
        WriteBufferSize: 1024,
    }

    http.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) {
        conn, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            log.Println(err)
            return
        }

        go webSocketHandler(conn)
    })

    log.Fatal(http.ListenAndServe(":9000", nil))
}
<|endoftext|>


<commit_before>/* Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package main

import (
	"strings"
	"bytes"
	"regexp"
	"strconv"
	"sort"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"go/build"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

const (
	projectDirName = "github.com/emepyc/guiHive"
)

var (
	port string
	isVersion = regexp.MustCompile(`^[0-9]+$`)
)

func init() {
	flag.StringVar(&port, "port", "8080", "Port to listen (defaults to 8080)")
	flag.Parse()
}

func checkError(s string, err error, ss ...string) {
	if err != nil {
		log.Fatal(s, err, ss)
	}
}

// Sortable os.fileInfos by name (-> num)
type sortableFiles []os.FileInfo
func (s sortableFiles) Len () int {
	return len(s)
}
func (s sortableFiles) Less (i, j int) bool {
	iVer, err := strconv.Atoi(s[i].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[i].Name()), err)
	jVer, err := strconv.Atoi(s[j].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[j].Name()), err)
	return iVer < jVer;
}
func (s sortableFiles) Swap (i, j int) {
	s[i], s[j] = s[j], s[i]
}

func version(r *http.Request) string {
	parts := strings.SplitN(r.URL.Path, "/", 4)
	version := parts[2]
	if (isVersion.MatchString(version)) {
		return version
	} else {
		path := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/"
		dir, err := os.Open(path)
		checkError("Can't open dir " + path, err)
		files, err := dir.Readdir(-1)
		checkError("Can't read dir " + path, err)
		sort.Sort(sortableFiles(files))
		version = files[len(files)-1].Name()
		return version
	}
	return ""
}

func unknown(w http.ResponseWriter, r *http.Request) {
	version := version(r)
	fmt.Fprintln(w, r.URL)
	fmt.Fprintf(w, "version %s is currently not supported by guiHive\n", version)
}

func scriptHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	defer r.Body.Close()
	checkError("Can't parse Form: ", err)

	debug("METHOD: %s", r.Method)
	debug("URL: %s", r.URL)

	var outMsg bytes.Buffer
	var errMsg bytes.Buffer

	fname := os.Getenv("GUIHIVE_PROJECTDIR") + r.URL.Path
	args, err := json.Marshal(r.Form)
	checkError("Can't Marshal JSON:", err)

	debug("EXECUTING SCRIPT: %s", fname)
	debug("ARGS: %s", args)
	version := version(r);
	debug("VERSION: %s", version)

	versionRootDir := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/" + version + "/";
	ehiveRootDir := versionRootDir + "/ensembl-hive"
	ehiveRootLib := ehiveRootDir + "/modules"
	guihiveRootLib := versionRootDir + "/scripts/lib"
	newPerl5Lib  := addPerl5Lib(ehiveRootLib + ":" + guihiveRootLib)

	debug("EHIVE_ROOT_DIR: %s", ehiveRootDir)
	debug("NEW_PERL5LIB: %s", newPerl5Lib)
	cmd := exec.Command(fname, string(args))
	cmd.Env = make([]string,0)
	cmd.Env = append(cmd.Env, "PERL5LIB=" + newPerl5Lib)
	cmd.Env = append(cmd.Env, "EHIVE_ROOT_DIR=" + ehiveRootDir)
	cmd.Env = append(cmd.Env, "GUIHIVE_BASEDIR=" + versionRootDir)
	cmd.Env = append(cmd.Env, "PATH=" + os.Getenv("PATH"))

	cmd.Stdout = &outMsg
	cmd.Stderr = &errMsg

	if err := cmd.Start(); err != nil {
		log.Println("Error Starting Command: ", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Println("Error Executing Command: ", err)
	}

	debug("OUTMSG: %s", outMsg.Bytes())
	debug("ERRMSG: %s", errMsg.Bytes())
	fmt.Fprintln(w, string(outMsg.Bytes()))

}

func pathExists(name string) bool {
	_, err := os.Stat(name)
	if os.IsNotExist(err) {
		return false
	}
	return err == nil
}

func guessProjectDir() (string, error) {
	// First, we try to find the project dir in the working directory
	serverPath := os.Args[0]
	serverDir := filepath.Dir(serverPath)
	pathToIndex := serverDir + "/../index.html"
	absPathToIndex, err := filepath.Abs(pathToIndex)
	if err != nil {
		debug("ABSPATHTOINDEX: %s\n", absPathToIndex)
		return "", err
	}
	if pathExists(absPathToIndex) {
		return path.Clean(absPathToIndex + "/.."), nil
	}
	for _, srcdir := range build.Default.SrcDirs() {
		dirName := path.Join(srcdir, projectDirName)
		fmt.Println("DIRNAME: ", dirName)
		if pathExists(dirName) {
			return dirName, nil
		}
	}
	return "", errors.New("Project directory not found")
}

func setEnvVar() error {
	projectDirectory, err := guessProjectDir()
	if err != nil {
		return err
	}
	//GUIHIVE_PROJECTDIR
	if err := os.Setenv("GUIHIVE_PROJECTDIR", projectDirectory+"/"); err != nil {
		return err
	}
	debug("PROJECT_DIRECTORY: %s\n", os.Getenv("GUIHIVE_PROJECTDIR"))

	// PER5LIB
	newPerl5Lib := addPerl5Lib(path.Clean(projectDirectory + "/scripts/lib"))
	err = setPerl5Lib(newPerl5Lib)
	if (err != nil) {
		return err
	}

	return nil
}

func addPerl5Lib (newDir string) string {
	perl5lib := os.Getenv("PERL5LIB")
	if perl5lib == "" {
		perl5lib = newDir
	} else {
		perl5lib = newDir + ":" + perl5lib
	}
	return perl5lib
}

func setPerl5Lib (perl5lib string) error {
	err := os.Setenv("PERL5LIB", perl5lib)
	if err != nil {
		return err
	}
	debug("PERL5LIB: %s\n", os.Getenv("PERL5LIB"))

	return nil
}

func main() {

	//  Fix environmental variables
	errV := setEnvVar()
	checkError("Problem setting environmental variables: ", errV)

	relPath := os.Getenv("GUIHIVE_PROJECTDIR")

	http.Handle("/", http.FileServer(http.Dir(relPath)))
	http.HandleFunc("/versions/", unknown)
	http.Handle("/styles/", http.FileServer(http.Dir(relPath)))
	http.Handle("/javascript/", http.FileServer(http.Dir(relPath)))
	http.Handle("/images/", http.FileServer(http.Dir(relPath)))
	http.HandleFunc("/scripts/", scriptHandler)

	versionRootDir := relPath + "/versions/"
	dir, terr := os.Open(versionRootDir)
	checkError("Can't open dir " + versionRootDir, terr)
	files, terr := dir.Readdir(-1)
	for _, verdir := range files {
		if (verdir.IsDir()) {
			debug("Found eHive version %s", verdir.Name())
			http.Handle(fmt.Sprintf("/versions/%s/", verdir.Name()), http.FileServer(http.Dir(relPath)))
			http.Handle(fmt.Sprintf("/versions/%s/javascript/", verdir.Name()), http.FileServer(http.Dir(relPath)))
			http.HandleFunc(fmt.Sprintf("/versions/%s/scripts/", verdir.Name()), scriptHandler)
		}
	}

	debug("Listening to port: %s", port)
	err := http.ListenAndServe(":"+port, nil)
	checkError("ListenAndServe ", err)
}
<commit_msg>Now accepts symlinks under versions/<commit_after>/* Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package main

import (
	"strings"
	"bytes"
	"regexp"
	"strconv"
	"sort"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"go/build"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

const (
	projectDirName = "github.com/emepyc/guiHive"
)

var (
	port string
	isVersion = regexp.MustCompile(`^[0-9]+$`)
)

func init() {
	flag.StringVar(&port, "port", "8080", "Port to listen (defaults to 8080)")
	flag.Parse()
}

func checkError(s string, err error, ss ...string) {
	if err != nil {
		log.Fatal(s, err, ss)
	}
}

// Sortable os.fileInfos by name (-> num)
type sortableFiles []os.FileInfo
func (s sortableFiles) Len () int {
	return len(s)
}
func (s sortableFiles) Less (i, j int) bool {
	iVer, err := strconv.Atoi(s[i].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[i].Name()), err)
	jVer, err := strconv.Atoi(s[j].Name())
	checkError(fmt.Sprintf("Dir name %s can't be converted to int", s[j].Name()), err)
	return iVer < jVer;
}
func (s sortableFiles) Swap (i, j int) {
	s[i], s[j] = s[j], s[i]
}

func version(r *http.Request) string {
	parts := strings.SplitN(r.URL.Path, "/", 4)
	version := parts[2]
	if (isVersion.MatchString(version)) {
		return version
	} else {
		path := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/"
		dir, err := os.Open(path)
		checkError("Can't open dir " + path, err)
		files, err := dir.Readdir(-1)
		checkError("Can't read dir " + path, err)
		sort.Sort(sortableFiles(files))
		version = files[len(files)-1].Name()
		return version
	}
	return ""
}

func unknown(w http.ResponseWriter, r *http.Request) {
	version := version(r)
	fmt.Fprintln(w, r.URL)
	fmt.Fprintf(w, "version %s is currently not supported by guiHive\n", version)
}

func scriptHandler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	defer r.Body.Close()
	checkError("Can't parse Form: ", err)

	debug("METHOD: %s", r.Method)
	debug("URL: %s", r.URL)

	var outMsg bytes.Buffer
	var errMsg bytes.Buffer

	fname := os.Getenv("GUIHIVE_PROJECTDIR") + r.URL.Path
	args, err := json.Marshal(r.Form)
	checkError("Can't Marshal JSON:", err)

	debug("EXECUTING SCRIPT: %s", fname)
	debug("ARGS: %s", args)
	version := version(r);
	debug("VERSION: %s", version)

	versionRootDir := os.Getenv("GUIHIVE_PROJECTDIR") + "/versions/" + version + "/";
	ehiveRootDir := versionRootDir + "/ensembl-hive"
	ehiveRootLib := ehiveRootDir + "/modules"
	guihiveRootLib := versionRootDir + "/scripts/lib"
	newPerl5Lib  := addPerl5Lib(ehiveRootLib + ":" + guihiveRootLib)

	debug("EHIVE_ROOT_DIR: %s", ehiveRootDir)
	debug("NEW_PERL5LIB: %s", newPerl5Lib)
	cmd := exec.Command(fname, string(args))
	cmd.Env = make([]string,0)
	cmd.Env = append(cmd.Env, "PERL5LIB=" + newPerl5Lib)
	cmd.Env = append(cmd.Env, "EHIVE_ROOT_DIR=" + ehiveRootDir)
	cmd.Env = append(cmd.Env, "GUIHIVE_BASEDIR=" + versionRootDir)
	cmd.Env = append(cmd.Env, "PATH=" + os.Getenv("PATH"))

	cmd.Stdout = &outMsg
	cmd.Stderr = &errMsg

	if err := cmd.Start(); err != nil {
		log.Println("Error Starting Command: ", err)
	}
	if err := cmd.Wait(); err != nil {
		log.Println("Error Executing Command: ", err)
	}

	debug("OUTMSG: %s", outMsg.Bytes())
	debug("ERRMSG: %s", errMsg.Bytes())
	fmt.Fprintln(w, string(outMsg.Bytes()))

}

func pathExists(name string) bool {
	_, err := os.Stat(name)
	if os.IsNotExist(err) {
		return false
	}
	return err == nil
}

func guessProjectDir() (string, error) {
	// First, we try to find the project dir in the working directory
	serverPath := os.Args[0]
	serverDir := filepath.Dir(serverPath)
	pathToIndex := serverDir + "/../index.html"
	absPathToIndex, err := filepath.Abs(pathToIndex)
	if err != nil {
		debug("ABSPATHTOINDEX: %s\n", absPathToIndex)
		return "", err
	}
	if pathExists(absPathToIndex) {
		return path.Clean(absPathToIndex + "/.."), nil
	}
	for _, srcdir := range build.Default.SrcDirs() {
		dirName := path.Join(srcdir, projectDirName)
		fmt.Println("DIRNAME: ", dirName)
		if pathExists(dirName) {
			return dirName, nil
		}
	}
	return "", errors.New("Project directory not found")
}

func setEnvVar() error {
	projectDirectory, err := guessProjectDir()
	if err != nil {
		return err
	}
	//GUIHIVE_PROJECTDIR
	if err := os.Setenv("GUIHIVE_PROJECTDIR", projectDirectory+"/"); err != nil {
		return err
	}
	debug("PROJECT_DIRECTORY: %s\n", os.Getenv("GUIHIVE_PROJECTDIR"))

	// PER5LIB
	newPerl5Lib := addPerl5Lib(path.Clean(projectDirectory + "/scripts/lib"))
	err = setPerl5Lib(newPerl5Lib)
	if (err != nil) {
		return err
	}

	return nil
}

func addPerl5Lib (newDir string) string {
	perl5lib := os.Getenv("PERL5LIB")
	if perl5lib == "" {
		perl5lib = newDir
	} else {
		perl5lib = newDir + ":" + perl5lib
	}
	return perl5lib
}

func setPerl5Lib (perl5lib string) error {
	err := os.Setenv("PERL5LIB", perl5lib)
	if err != nil {
		return err
	}
	debug("PERL5LIB: %s\n", os.Getenv("PERL5LIB"))

	return nil
}

func main() {

	//  Fix environmental variables
	errV := setEnvVar()
	checkError("Problem setting environmental variables: ", errV)

	relPath := os.Getenv("GUIHIVE_PROJECTDIR")

	http.Handle("/", http.FileServer(http.Dir(relPath)))
	http.HandleFunc("/versions/", unknown)
	http.Handle("/styles/", http.FileServer(http.Dir(relPath)))
	http.Handle("/javascript/", http.FileServer(http.Dir(relPath)))
	http.Handle("/images/", http.FileServer(http.Dir(relPath)))
	http.HandleFunc("/scripts/", scriptHandler)

	versionRootDir := relPath + "/versions/"
	dir, terr := os.Open(versionRootDir)
	checkError("Can't open dir " + versionRootDir, terr)
	files, terr := dir.Readdir(-1)
	for _, verdir := range files {
		if ((verdir.Mode() & os.ModeSymlink) != 0) {
			targetF, _ := os.Lstat(versionRootDir + verdir.Name())
			debug("Found a symlink from %s to version %s", verdir.Name(), targetF.Name())
			verdir = targetF
		} else if (verdir.IsDir()) {
			debug("Found eHive version %s", verdir.Name())
			http.Handle(fmt.Sprintf("/versions/%s/", verdir.Name()), http.FileServer(http.Dir(relPath)))
			http.Handle(fmt.Sprintf("/versions/%s/javascript/", verdir.Name()), http.FileServer(http.Dir(relPath)))
			http.HandleFunc(fmt.Sprintf("/versions/%s/scripts/", verdir.Name()), scriptHandler)
		}
	}

	debug("Listening to port: %s", port)
	err := http.ListenAndServe(":"+port, nil)
	checkError("ListenAndServe ", err)
}
<|endoftext|>


<commit_before>// Copyright 2015 realglobe, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// HTTP サーバー関係。
package server

import (
	"github.com/realglobe-Inc/go-lib/erro"
	"math/rand"
	"net"
	"net/http"
	"net/http/fcgi"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"syscall"
	"time"
)

// Serve に入力するパラメータ。
type Parameter interface {
	// ソケットの種類。tcp か unix。
	SocketType() string
	// プロトコルの種類。http か fcgi。
	ProtocolType() string
}

// SocketType が tcp のときに追加で必要な関数。
type TcpParameter interface {
	// tcp のポート番号。
	SocketPort() int
}

// SocketType が unix のときに追加で必要な関数。
type UnixParameter interface {
	// unix のファイルパス。
	SocketPath() string
}

// デバッグに使える関数。
type DebugParameter interface {
	ShutdownChannel() chan struct{}
}

// 冷却期間の最大値。
var MaxSleepTime = time.Minute

// この期間以上エラー無しで動作したら冷却期間を 0 にする。
var ResetInterval = time.Minute

// サーバーを立てる。
func Serve(param Parameter, handler http.Handler) error {
	// 冷却期間の揺らぎの最大値。
	// 冷却期間は前回の 2 倍に一様乱数の揺らぎを加えたものになる。
	const fluct = 100 * time.Millisecond

	var serv func(net.Listener, http.Handler) error
	switch param.ProtocolType() {
	case "http":
		serv = http.Serve
	case "fcgi":
		serv = fcgi.Serve
	default:
		return erro.New("invalid protocol type " + param.ProtocolType())
	}

	var shutCh chan struct{}
	if p, ok := param.(DebugParameter); ok {
		shutCh = p.ShutdownChannel()
	} else {
		shutCh = make(chan struct{}, 10) // 余裕を持って。
	}

	var lis net.Listener
	var lisLock sync.Mutex

	// SIGINT、SIGTERM を受け取ったら正常終了。
	sigCh := make(chan os.Signal, 10)
	signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)

	// 別スレッドで終了を監視する。
	go func() {
		select {
		case sig := <-sigCh:
			log.Info("Signal ", sig, " was detected")
		case <-shutCh:
			log.Info("Shutdown was operated")
		}
		shutCh <- struct{}{}
		lisLock.Lock()
		l := lis
		lisLock.Unlock()

		if l != nil {
			l.Close()
			log.Info("Socket was closed")
		} else {
			log.Info("No socket to close")
		}
	}()

	var sleepTime time.Duration = 0
	for {
		if retry, err := func() (retry bool, err error) {

			var l net.Listener
			defer func() {
				if l != nil {
					l.Close()
				}
			}()

			switch param.SocketType() {
			case "unix":
				p, ok := param.(UnixParameter)
				if !ok {
					return false, erro.New("SocketPath function is not implemented")
				}
				l, err = net.Listen("unix", p.SocketPath())
				if err != nil {
					return true, erro.Wrap(err)
				} else if err := os.Chmod(p.SocketPath(), 0777); err != nil {
					return true, erro.Wrap(err)
				}
				log.Info("Wait on UNIX socket " + p.SocketPath())
			case "tcp":
				p, ok := param.(TcpParameter)
				if !ok {
					return false, erro.New("SocketPort function is not implemented")
				}
				l, err = net.Listen("tcp", ":"+strconv.Itoa(p.SocketPort()))
				if err != nil {
					return true, erro.Wrap(err)
				}
				log.Info("Wait on TCP socket ", p.SocketPort())
			default:
				return false, erro.New("invalid socket type " + param.SocketType())
			}

			lisLock.Lock()
			lis = l
			lisLock.Unlock()
			select {
			case <-shutCh:
				// 既に終了信号を受け取っていた。
				shutCh <- struct{}{}
				return false, nil
			default:
			}

			start := time.Now()
			if err := func() error {
				log.Debug("Service starts")
				defer log.Debug("Service exits")
				return erro.Wrap(serv(l, handler))
			}(); err != nil {
				err := erro.Wrap(err)

				select {
				case <-shutCh:
					// 既に終了信号を受け取っていた。
					shutCh <- struct{}{}
					return false, nil
				default:
				}

				end := time.Now()
				if end.Sub(start) > ResetInterval {
					sleepTime = 0
				}

				return true, err
			}

			return true, nil
		}(); !retry {
			return erro.Wrap(err)
		} else {
			if err != nil {
				log.Err(erro.Unwrap(err))
				log.Debug(err)
			}

			sleepTime = nextSleepTime(sleepTime, fluct, MaxSleepTime)
			log.Info("Sleep ", sleepTime)

			timeCh := time.After(sleepTime)
			select {
			case <-shutCh:
				shutCh <- struct{}{}
				return nil
			case <-timeCh:
			}
		}
	}

	return nil
}

func nextSleepTime(cur, fluc, max time.Duration) time.Duration {
	next := 2*cur + time.Duration(rand.Int63n(int64(fluc)))
	if next > max {
		next = max
	}
	return next
}
<commit_msg>テストと本番でパラメータ型を共有できるようにした<commit_after>// Copyright 2015 realglobe, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// HTTP サーバー関係。
package server

import (
	"github.com/realglobe-Inc/go-lib/erro"
	"math/rand"
	"net"
	"net/http"
	"net/http/fcgi"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"syscall"
	"time"
)

// Serve に入力するパラメータ。
type Parameter interface {
	// ソケットの種類。tcp か unix。
	SocketType() string
	// プロトコルの種類。http か fcgi。
	ProtocolType() string
}

// SocketType が tcp のときに追加で必要な関数。
type TcpParameter interface {
	// tcp のポート番号。
	SocketPort() int
}

// SocketType が unix のときに追加で必要な関数。
type UnixParameter interface {
	// unix のファイルパス。
	SocketPath() string
}

// デバッグに使える関数。
type DebugParameter interface {
	ShutdownChannel() chan struct{}
}

// 冷却期間の最大値。
var MaxSleepTime = time.Minute

// この期間以上エラー無しで動作したら冷却期間を 0 にする。
var ResetInterval = time.Minute

// サーバーを立てる。
func Serve(param Parameter, handler http.Handler) error {
	// 冷却期間の揺らぎの最大値。
	// 冷却期間は前回の 2 倍に一様乱数の揺らぎを加えたものになる。
	const fluct = 100 * time.Millisecond

	var serv func(net.Listener, http.Handler) error
	switch param.ProtocolType() {
	case "http":
		serv = http.Serve
	case "fcgi":
		serv = fcgi.Serve
	default:
		return erro.New("invalid protocol type " + param.ProtocolType())
	}

	var shutCh chan struct{}
	if p, ok := param.(DebugParameter); ok {
		shutCh = p.ShutdownChannel()
	}
	if shutCh == nil {
		shutCh = make(chan struct{}, 10) // 余裕を持って。
	}

	var lis net.Listener
	var lisLock sync.Mutex

	// SIGINT、SIGTERM を受け取ったら正常終了。
	sigCh := make(chan os.Signal, 10)
	signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)

	// 別スレッドで終了を監視する。
	go func() {
		select {
		case sig := <-sigCh:
			log.Info("Signal ", sig, " was detected")
		case <-shutCh:
			log.Info("Shutdown was operated")
		}
		shutCh <- struct{}{}
		lisLock.Lock()
		l := lis
		lisLock.Unlock()

		if l != nil {
			l.Close()
			log.Info("Socket was closed")
		} else {
			log.Info("No socket to close")
		}
	}()

	var sleepTime time.Duration = 0
	for {
		if retry, err := func() (retry bool, err error) {

			var l net.Listener
			defer func() {
				if l != nil {
					l.Close()
				}
			}()

			switch param.SocketType() {
			case "unix":
				p, ok := param.(UnixParameter)
				if !ok {
					return false, erro.New("SocketPath function is not implemented")
				}
				l, err = net.Listen("unix", p.SocketPath())
				if err != nil {
					return true, erro.Wrap(err)
				} else if err := os.Chmod(p.SocketPath(), 0777); err != nil {
					return true, erro.Wrap(err)
				}
				log.Info("Wait on UNIX socket " + p.SocketPath())
			case "tcp":
				p, ok := param.(TcpParameter)
				if !ok {
					return false, erro.New("SocketPort function is not implemented")
				}
				l, err = net.Listen("tcp", ":"+strconv.Itoa(p.SocketPort()))
				if err != nil {
					return true, erro.Wrap(err)
				}
				log.Info("Wait on TCP socket ", p.SocketPort())
			default:
				return false, erro.New("invalid socket type " + param.SocketType())
			}

			lisLock.Lock()
			lis = l
			lisLock.Unlock()
			select {
			case <-shutCh:
				// 既に終了信号を受け取っていた。
				shutCh <- struct{}{}
				return false, nil
			default:
			}

			start := time.Now()
			if err := func() error {
				log.Debug("Service starts")
				defer log.Debug("Service exits")
				return erro.Wrap(serv(l, handler))
			}(); err != nil {
				err := erro.Wrap(err)

				select {
				case <-shutCh:
					// 既に終了信号を受け取っていた。
					shutCh <- struct{}{}
					return false, nil
				default:
				}

				end := time.Now()
				if end.Sub(start) > ResetInterval {
					sleepTime = 0
				}

				return true, err
			}

			return true, nil
		}(); !retry {
			return erro.Wrap(err)
		} else {
			if err != nil {
				log.Err(erro.Unwrap(err))
				log.Debug(err)
			}

			sleepTime = nextSleepTime(sleepTime, fluct, MaxSleepTime)
			log.Info("Sleep ", sleepTime)

			timeCh := time.After(sleepTime)
			select {
			case <-shutCh:
				shutCh <- struct{}{}
				return nil
			case <-timeCh:
			}
		}
	}

	return nil
}

func nextSleepTime(cur, fluc, max time.Duration) time.Duration {
	next := 2*cur + time.Duration(rand.Int63n(int64(fluc)))
	if next > max {
		next = max
	}
	return next
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"

	"github.com/gtfierro/hod/config"
	hod "github.com/gtfierro/hod/db"
	"github.com/gtfierro/hod/query"

	"github.com/julienschmidt/httprouter"
	"github.com/op/go-logging"
	"github.com/pkg/profile"
	"golang.org/x/crypto/acme/autocert"
)

// logger
var log *logging.Logger

// set up logging facilities
func init() {
	log = logging.MustGetLogger("http")
	var format = "%{color}%{level} %{time:Jan 02 15:04:05} %{shortfile}%{color:reset} ▶ %{message}"
	var logBackend = logging.NewLogBackend(os.Stderr, "", 0)
	logBackendLeveled := logging.AddModuleLevel(logBackend)
	logging.SetBackend(logBackendLeveled)
	logging.SetFormatter(logging.MustStringFormatter(format))
}

type hodServer struct {
	db         *hod.DB
	port       string
	staticpath string
	router     *httprouter.Router
}

func StartHodServer(db *hod.DB, cfg *config.Config) {
	server := &hodServer{
		db:         db,
		port:       cfg.ServerPort,
		staticpath: cfg.StaticPath,
	}
	r := httprouter.New()

	// TODO: how do we handle loading in data? Need to have the multiple
	// concurrent buildings issue fixed first, but for now it is sufficient
	// to just have one server per building
	r.POST("/api/query", server.handleQuery)
	r.POST("/api/loadlinks", server.handleLoadLinks)
	r.POST("/api/querydot", server.handleQueryDot)
	r.POST("/api/queryclassdot", server.handleQueryClassDot)
	r.ServeFiles("/static/*filepath", http.Dir(cfg.StaticPath+"/static"))
	r.GET("/", server.serveQuery)
	r.GET("/query", server.serveQuery)
	r.GET("/help", server.serveHelp)
	r.GET("/plan", server.servePlanner)
	r.GET("/explore", server.serveExplorer)
	server.router = r

	// enable profiling if configured
	if cfg.EnableCPUProfile {
		defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
	} else if cfg.EnableMEMProfile {
		defer profile.Start(profile.MemProfile, profile.ProfilePath(".")).Stop()
	} else if cfg.EnableBlockProfile {
		defer profile.Start(profile.BlockProfile, profile.ProfilePath(".")).Stop()
	}

	// configure server
	var (
		addrString string
		nettype    string
	)

	// check if ipv6
	if cfg.UseIPv6 {
		nettype = "tcp6"
		addrString = "[" + cfg.ListenAddress + "]:" + server.port
	} else {
		nettype = "tcp4"
		addrString = cfg.ListenAddress + ":" + server.port
	}

	address, err := net.ResolveTCPAddr(nettype, addrString)
	if err != nil {
		log.Fatalf("Error resolving address %s (%s)", addrString, err.Error())
	}

	http.Handle("/", server.router)
	log.Notice("Starting HTTP Server on ", addrString)

	if cfg.TLSHost != "" {
		m := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			HostPolicy: autocert.HostWhitelist(cfg.TLSHost),
		}
		s := &http.Server{
			Addr:      address.String(),
			TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
		}
		log.Fatal(s.ListenAndServeTLS("", ""))
	} else {
		srv := &http.Server{
			Addr: address.String(),
		}
		log.Fatal(srv.ListenAndServe())
	}
}

func (srv *hodServer) handleQuery(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()

	log.Infof("Query from %s", req.RemoteAddr)
	parsed, err := query.Parse(req.Body)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(400)
		rw.Write([]byte(err.Error()))
		return
	}

	// evaluate query
	res := srv.db.RunQuery(parsed)

	encoder := json.NewEncoder(rw)
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	err = encoder.Encode(res)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(500)
		rw.Write([]byte(err.Error()))
		return
	}
	return
}

func (srv *hodServer) handleLoadLinks(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()
	log.Infof("LoadLinks from %s", req.RemoteAddr)

	var updates = new(hod.LinkUpdates)
	decoder := json.NewDecoder(req.Body)
	if err := decoder.Decode(updates); err != nil {
		log.Error(err)
		rw.WriteHeader(500)
		rw.Write([]byte(err.Error()))
		return
	}

	_, err := rw.Write([]byte(fmt.Sprintf("Adding %d links, Removing %d links", len(updates.Adding), len(updates.Removing))))
	if err != nil {
		log.Error(err)
		rw.WriteHeader(500)
		rw.Write([]byte(err.Error()))
		return
	}
	return
}

func (srv *hodServer) serveHelp(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve help from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/help.html")
}

func (srv *hodServer) serveQuery(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve query from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/query.html")
}

func (srv *hodServer) servePlanner(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve planner from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/plan.html")
}

func (srv *hodServer) serveExplorer(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve explorer from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/explore.html")
}

func (srv *hodServer) handleQueryDot(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()
	log.Infof("QueryDot from %s", req.RemoteAddr)

	dot, err := srv.db.QueryToDOT(req.Body)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(400)
		rw.Write([]byte(err.Error()))
		return
	}

	rw.Write([]byte(dot))
	return
}

func (srv *hodServer) handleQueryClassDot(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()
	log.Infof("QueryDot from %s", req.RemoteAddr)

	dot, err := srv.db.QueryToClassDOT(req.Body)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(400)
		rw.Write([]byte(err.Error()))
		return
	}

	rw.Write([]byte(dot))
	return
}
<commit_msg>add cert cache<commit_after>package server

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"os"

	"github.com/gtfierro/hod/config"
	hod "github.com/gtfierro/hod/db"
	"github.com/gtfierro/hod/query"

	"github.com/julienschmidt/httprouter"
	"github.com/op/go-logging"
	"github.com/pkg/profile"
	"golang.org/x/crypto/acme/autocert"
)

// logger
var log *logging.Logger

// set up logging facilities
func init() {
	log = logging.MustGetLogger("http")
	var format = "%{color}%{level} %{time:Jan 02 15:04:05} %{shortfile}%{color:reset} ▶ %{message}"
	var logBackend = logging.NewLogBackend(os.Stderr, "", 0)
	logBackendLeveled := logging.AddModuleLevel(logBackend)
	logging.SetBackend(logBackendLeveled)
	logging.SetFormatter(logging.MustStringFormatter(format))
}

type hodServer struct {
	db         *hod.DB
	port       string
	staticpath string
	router     *httprouter.Router
}

func StartHodServer(db *hod.DB, cfg *config.Config) {
	server := &hodServer{
		db:         db,
		port:       cfg.ServerPort,
		staticpath: cfg.StaticPath,
	}
	r := httprouter.New()

	// TODO: how do we handle loading in data? Need to have the multiple
	// concurrent buildings issue fixed first, but for now it is sufficient
	// to just have one server per building
	r.POST("/api/query", server.handleQuery)
	r.POST("/api/loadlinks", server.handleLoadLinks)
	r.POST("/api/querydot", server.handleQueryDot)
	r.POST("/api/queryclassdot", server.handleQueryClassDot)
	r.ServeFiles("/static/*filepath", http.Dir(cfg.StaticPath+"/static"))
	r.GET("/", server.serveQuery)
	r.GET("/query", server.serveQuery)
	r.GET("/help", server.serveHelp)
	r.GET("/plan", server.servePlanner)
	r.GET("/explore", server.serveExplorer)
	server.router = r

	// enable profiling if configured
	if cfg.EnableCPUProfile {
		defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
	} else if cfg.EnableMEMProfile {
		defer profile.Start(profile.MemProfile, profile.ProfilePath(".")).Stop()
	} else if cfg.EnableBlockProfile {
		defer profile.Start(profile.BlockProfile, profile.ProfilePath(".")).Stop()
	}

	// configure server
	var (
		addrString string
		nettype    string
	)

	// check if ipv6
	if cfg.UseIPv6 {
		nettype = "tcp6"
		addrString = "[" + cfg.ListenAddress + "]:" + server.port
	} else {
		nettype = "tcp4"
		addrString = cfg.ListenAddress + ":" + server.port
	}

	address, err := net.ResolveTCPAddr(nettype, addrString)
	if err != nil {
		log.Fatalf("Error resolving address %s (%s)", addrString, err.Error())
	}

	http.Handle("/", server.router)
	log.Notice("Starting HTTP Server on ", addrString)

	if cfg.TLSHost != "" {
		m := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			HostPolicy: autocert.HostWhitelist(cfg.TLSHost),
			Cache:      autocert.DirCache("certs"),
		}
		s := &http.Server{
			Addr:      address.String(),
			TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
		}
		log.Fatal(s.ListenAndServeTLS("", ""))
	} else {
		srv := &http.Server{
			Addr: address.String(),
		}
		log.Fatal(srv.ListenAndServe())
	}
}

func (srv *hodServer) handleQuery(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()

	log.Infof("Query from %s", req.RemoteAddr)
	parsed, err := query.Parse(req.Body)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(400)
		rw.Write([]byte(err.Error()))
		return
	}

	// evaluate query
	res := srv.db.RunQuery(parsed)

	encoder := json.NewEncoder(rw)
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	err = encoder.Encode(res)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(500)
		rw.Write([]byte(err.Error()))
		return
	}
	return
}

func (srv *hodServer) handleLoadLinks(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()
	log.Infof("LoadLinks from %s", req.RemoteAddr)

	var updates = new(hod.LinkUpdates)
	decoder := json.NewDecoder(req.Body)
	if err := decoder.Decode(updates); err != nil {
		log.Error(err)
		rw.WriteHeader(500)
		rw.Write([]byte(err.Error()))
		return
	}

	_, err := rw.Write([]byte(fmt.Sprintf("Adding %d links, Removing %d links", len(updates.Adding), len(updates.Removing))))
	if err != nil {
		log.Error(err)
		rw.WriteHeader(500)
		rw.Write([]byte(err.Error()))
		return
	}
	return
}

func (srv *hodServer) serveHelp(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve help from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/help.html")
}

func (srv *hodServer) serveQuery(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve query from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/query.html")
}

func (srv *hodServer) servePlanner(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve planner from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/plan.html")
}

func (srv *hodServer) serveExplorer(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	log.Infof("Serve explorer from %s", req.RemoteAddr)
	defer req.Body.Close()
	http.ServeFile(rw, req, srv.staticpath+"/explore.html")
}

func (srv *hodServer) handleQueryDot(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()
	log.Infof("QueryDot from %s", req.RemoteAddr)

	dot, err := srv.db.QueryToDOT(req.Body)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(400)
		rw.Write([]byte(err.Error()))
		return
	}

	rw.Write([]byte(dot))
	return
}

func (srv *hodServer) handleQueryClassDot(rw http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	defer req.Body.Close()
	log.Infof("QueryDot from %s", req.RemoteAddr)

	dot, err := srv.db.QueryToClassDOT(req.Body)
	if err != nil {
		log.Error(err)
		rw.WriteHeader(400)
		rw.Write([]byte(err.Error()))
		return
	}

	rw.Write([]byte(dot))
	return
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"encoding/json"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/pkg/errors"
	"github.com/travisjeffery/jocko/broker"
	"github.com/travisjeffery/jocko/cluster"
	"github.com/travisjeffery/jocko/commitlog"
	"github.com/travisjeffery/jocko/protocol"
)

type MetadataRequest struct {
	Topics []string `json:"topics"`
}

type Broker struct {
	ID   string `json:"id"`
	Host string `json:"host"`
	Port string `json:"port"`
}

type PartitionMetadata struct {
	ErrorCode int      `json:"error_code"`
	ID        int32    `json:"id"`
	Leader    string   `json:"leader"`
	Replicas  []string `json:"replicas"`
}

type TopicMetadata struct {
	ErrorCode         int                 `json:"error_code"`
	Topic             string              `json:"topic"`
	PartitionMetadata []PartitionMetadata `json:"partition_metadata"`
}

type MetadataResponse struct {
	Brokers       []Broker        `json:"brokers"`
	ControllerID  string          `json:"controller_id"`
	TopicMetadata []TopicMetadata `json:"topic_metadata"`
}

type Server struct {
	addr string
	ln   *net.TCPListener

	logger *log.Logger
	broker *broker.Broker
}

func New(addr string, broker *broker.Broker) *Server {
	logger := log.New(os.Stderr, "server", log.LstdFlags)
	return &Server{
		addr:   addr,
		broker: broker,
		logger: logger,
	}
}

// Start starts the service.
func (s *Server) Start() error {
	addr, err := net.ResolveTCPAddr("tcp", s.addr)
	if err != nil {
		panic(err)
	}

	ln, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return err
	}
	s.ln = ln

	r := mux.NewRouter()
	r.Methods("POST").Path("/join").HandlerFunc(s.handleJoin)

	// r.Methods("POST").Path("/metadata").HandlerFunc(s.handleMetadata)
	// r.Methods("POST").Path("/metadata/topic").HandlerFunc(s.handleTopic)
	// 	r.Methods("POST").Path("/produce").HandlerFunc(s.handleProduce)
	// r.Methods("POST").Path("/fetch").HandlerFunc(s.handleFetch)
	r.PathPrefix("").HandlerFunc(s.handleNotFound)
	http.Handle("/", r)

	loggedRouter := handlers.LoggingHandler(os.Stdout, r)

	server := http.Server{
		Handler: loggedRouter,
	}

	go func() {
		conn, err := s.ln.Accept()
		if err != nil {
			s.logger.Fatalf("Listener accept failed: %s", err)
		}

		go s.handleRequest(conn)
	}()

	go func() {
		err := server.Serve(s.ln)
		if err != nil {
			s.logger.Fatalf("HTTP serve: %s", err)
		}
	}()

	return nil
}

// Close closes the service.
func (s *Server) Close() {
	s.ln.Close()
	return
}

func (s *Server) handleRequest(conn net.Conn) {
	defer conn.Close()

	header := new(protocol.RequestHeader)
	p := make([]byte, 4)

	for {
		err := conn.SetReadDeadline(time.Now().Add(5 * time.Second))
		if err != nil {
			s.logger.Printf("read deadline failed: %s", err)
			continue
		}

		n, err := io.ReadFull(conn, p[:])
		if err == io.EOF {
			break
		}
		if n == 0 || err != nil {
			// TODO: handle err
			s.logger.Printf("Conn read failed: %s", err)
			continue
		}

		size := protocol.Encoding.Uint32(p)
		if size == 0 {
			continue
		}
		b := make([]byte, size+4) //+4 since we're going to copy the size into b
		copy(b, p)

		_, err = io.ReadFull(conn, b[4:])
		if err != nil {
			panic(err)
		}

		d := protocol.NewDecoder(b)
		header.Decode(d)

		switch header.APIKey {
		case 0:
			req := &protocol.ProduceRequest{}
			req.Decode(d)
			if err = s.handleProduce(conn, header, req); err != nil {
				s.logger.Printf("produce failed: %s", err)
			}
		case 1:
			req := &protocol.FetchRequest{}
			req.Decode(d)
			if err = s.handleFetch(conn, header, req); err != nil {
				s.logger.Printf("fetch failed: %s", err)
			}
		case 3:
			req := &protocol.MetadataRequest{}
			req.Decode(d)
			if err = s.handleMetadata(conn, header, req); err != nil {
				s.logger.Printf("metadata failed: %s", err)
			}
		case 19:
			req := &protocol.CreateTopicRequests{}
			req.Decode(d)
			if err = s.handleCreateTopic(conn, header, req); err != nil {
				s.logger.Printf("create topic failed: %s", err)
			}
		}
	}
}

func (s *Server) handleCreateTopic(conn net.Conn, header *protocol.RequestHeader, reqs *protocol.CreateTopicRequests) (err error) {
	resp := new(protocol.CreateTopicsResponse)
	resp.TopicErrorCodes = make([]*protocol.TopicErrorCode, len(reqs.Requests))

	if s.broker.IsController() {
		for i, req := range reqs.Requests {
			err = s.broker.CreateTopic(req.Topic, req.NumPartitions)
			if err != nil {
				s.logger.Printf("[ERR] jocko: Failed to create topic %s: %v", req.Topic, err)
				return
			}
			resp.TopicErrorCodes[i] = &protocol.TopicErrorCode{
				Topic:     req.Topic,
				ErrorCode: protocol.ErrNone,
			}
		}
	} else {
		// cID := s.broker.ControllerID()
		// send the request to the controller
		return
	}
	r := &protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	}

	b, err := protocol.Encode(r)

	if err != nil {
		return err
	}

	_, err = conn.Write(b)

	return err
}

func zero(p []byte) {
	for i := range p {
		p[i] = 0
	}
}

func (s *Server) handleJoin(w http.ResponseWriter, r *http.Request) {
	m := map[string]string{}
	if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	if len(m) != 1 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	remoteAddr, ok := m["addr"]
	if !ok {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if err := s.broker.Join(remoteAddr); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}

func (s *Server) handleMetadata(conn net.Conn, header *protocol.RequestHeader, req *protocol.MetadataRequest) error {
	brokerIDs, err := s.broker.Brokers()
	brokers := make([]*protocol.Broker, len(brokerIDs))
	topics := make([]*protocol.TopicMetadata, len(req.Topics))
	for i, bID := range brokerIDs {
		host, port, err := net.SplitHostPort(bID)
		if err != nil {
			return err
		}
		// TODO: replace this with an actual id
		nodeID := i
		if err != nil {
			return err
		}
		nPort, err := strconv.Atoi(port)
		if err != nil {
			return err
		}
		brokers[i] = &protocol.Broker{
			NodeID: int32(nodeID),
			Host:   host,
			Port:   int32(nPort),
		}
	}
	for i, t := range req.Topics {
		partitions, err := s.broker.PartitionsForTopic(t)
		if err != nil {
			return err
		}
		partitionMetadata := make([]*protocol.PartitionMetadata, len(partitions))
		for i, p := range partitions {
			partitionMetadata[i] = &protocol.PartitionMetadata{
				ParititionID: p.Partition,
			}
		}
		topics[i] = &protocol.TopicMetadata{
			TopicErrorCode:    protocol.ErrNone,
			Topic:             t,
			PartitionMetadata: partitionMetadata,
		}
	}
	resp := &protocol.MetadataResponse{
		Brokers:       brokers,
		TopicMetadata: topics,
	}
	r := &protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	}
	b, err := protocol.Encode(r)
	if err != nil {
		return err
	}
	_, err = conn.Write(b)
	return err
}

func (s *Server) handleNotFound(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
}

type TopicRequest struct {
	Topic      string `json:"topic"`
	Partitions int    `json"partitions"`
}

type ProduceRequest struct {
	RequiredAcks int                  `json:"required_acks"`
	Timeout      int                  `json:"timeout"`
	Partition    int32                `json:"partition"`
	Topic        string               `json:"topic"`
	MessageSet   commitlog.MessageSet `json:"message_set"`
}

type ProduceResponse struct {
	Topic     string `json:"topic"`
	Partition int32  `json:"partition"`
	ErrorCode int16  `json:"error_code"`
	Offset    int64  `json:"offset"`
	Timestamp int64  `json:"timestamp"`
}

func (s *Server) handleProduce(conn net.Conn, header *protocol.RequestHeader, r *protocol.ProduceRequest) error {
	resp := new(protocol.ProduceResponses)
	resp.Responses = make([]*protocol.ProduceResponse, len(r.TopicData))
	for i, td := range r.TopicData {
		presps := make([]*protocol.ProducePartitionresponse, len(td.Data))
		for j, p := range td.Data {
			partition := &cluster.TopicPartition{
				Topic:     td.Topic,
				Partition: p.Partition,
			}
			presp := &protocol.ProducePartitionresponse{}
			partition, err := s.broker.Partition(td.Topic, p.Partition)
			if err != nil {
				presp.ErrorCode = protocol.ErrUnknownTopicOrPartition
			}
			if !s.broker.IsLeaderOfPartition(partition) {
				presp.ErrorCode = protocol.ErrNotLeaderForPartition
			}
			err = partition.CommitLog.Append(p.RecordSet)
			if err != nil {
				s.logger.Printf("commitlog append failed: %s", err)
				presp.ErrorCode = protocol.ErrUnknown
			}
			presp.Partition = p.Partition
			// TODO: presp.BaseOffset
			presp.Timestamp = time.Now().Unix()
			presps[j] = presp
		}
		resp.Responses[i] = &protocol.ProduceResponse{
			Topic:              td.Topic,
			PartitionResponses: presps,
		}
	}

	b, err := protocol.Encode(&protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	})
	if err != nil {
		return err
	}
	_, err = conn.Write(b)
	return err
}

type FetchRequest struct {
	Topic       string `json:"topic"`
	Partition   int32  `json:"partition"`
	FetchOffset int64  `json:"offset"`
	MinBytes    int32  `json:"min_bytes"`
	MaxBytes    int32  `json:"max_bytes"`
	MaxWaitTime int32  `json:"max_wait_time"` // in ms
}

type FetchResponse struct {
	Topic     string `json:"topic"`
	Partition int32  `json:"partition"`
	// b in bytes
	MessageSetSize int32                `json:"message_set_size"`
	MessageSet     commitlog.MessageSet `json:"message_set"`
}

func (s *Server) handleFetch(conn net.Conn, header *protocol.RequestHeader, r *protocol.FetchRequest) error {
	resp := &protocol.FetchResponses{}
	resp.Responses = make([]*protocol.FetchResponse, len(r.Topics))
	received := time.Now()
	for i, topic := range r.Topics {
		fr := &protocol.FetchResponse{}
		resp.Responses[i] = fr
		fr.PartitionResponses = make([]*protocol.FetchPartitionResponse, len(topic.Partitions))
		for j, p := range topic.Partitions {
			partition, err := s.broker.Partition(topic.Topic, p.Partition)
			if err != nil {
				// TODO set err code
				s.logger.Printf("[ERR] jocko: Failed to find partition: %v (%s/%d)", err, topic.Topic, p.Partition)
				break
			}
			if !s.broker.IsLeaderOfPartition(partition) {
				s.logger.Printf("[ERR] jocko: Failed to produce: %v", errors.New("broker is not partition leader"))
				// TODO set err code
				break
			}
			rdr, err := partition.CommitLog.NewReader(commitlog.ReaderOptions{
				Offset:   p.FetchOffset,
				MaxBytes: p.MaxBytes,
			})
			if err != nil {
				s.logger.Printf("[ERR] jocko: Failed to read partition: %v", err)
				// TODO set err code
				break
			}
			b := bytes.NewBuffer(make([]byte, 0))
			var n int32
			for n < r.MinBytes {
				if r.MaxWaitTime != 0 && int32(time.Since(received).Nanoseconds()/1e6) > r.MaxWaitTime {
					break
				}

				// TODO: copy these bytes to outer bytes
				nn, err := io.Copy(b, rdr)
				if err != nil && err != io.EOF {
					s.logger.Printf("[ERR] jocko: Failed to fetch messages: %v", err)
					// TODO seT error code
					break
				}
				n += int32(nn)
				if err == io.EOF {
					break
				}
			}
			fr.PartitionResponses[j] = &protocol.FetchPartitionResponse{
				Partition:     p.Partition,
				ErrorCode:     protocol.ErrNone,
				HighWatermark: 0, // TODO get last committed offset
				RecordSet:     b.Bytes(),
			}
		}
	}

	b, err := protocol.Encode(&protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	})
	if err != nil {
		return err
	}
	_, err = conn.Write(b)
	return err
}

// Addr returns the address on which the Server is listening
func (s *Server) Addr() net.Addr {
	return s.ln.Addr()
}

func writeJSON(w http.ResponseWriter, v interface{}, code ...int) {
	var b []byte
	var err error
	b, err = json.Marshal(v)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	if len(code) > 0 {
		w.WriteHeader(code[0])
	}
	w.Write(b)
}
<commit_msg>remove unused http stuff<commit_after>package server

import (
	"bytes"
	"encoding/json"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/pkg/errors"
	"github.com/travisjeffery/jocko/broker"
	"github.com/travisjeffery/jocko/cluster"
	"github.com/travisjeffery/jocko/commitlog"
	"github.com/travisjeffery/jocko/protocol"
)

type Broker struct {
	ID   string `json:"id"`
	Host string `json:"host"`
	Port string `json:"port"`
}

type Server struct {
	addr string
	ln   *net.TCPListener

	logger *log.Logger
	broker *broker.Broker
}

func New(addr string, broker *broker.Broker) *Server {
	logger := log.New(os.Stderr, "server", log.LstdFlags)
	return &Server{
		addr:   addr,
		broker: broker,
		logger: logger,
	}
}

// Start starts the service.
func (s *Server) Start() error {
	addr, err := net.ResolveTCPAddr("tcp", s.addr)
	if err != nil {
		panic(err)
	}

	ln, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return err
	}
	s.ln = ln

	r := mux.NewRouter()
	r.Methods("POST").Path("/join").HandlerFunc(s.handleJoin)
	r.PathPrefix("").HandlerFunc(s.handleNotFound)
	http.Handle("/", r)

	loggedRouter := handlers.LoggingHandler(os.Stdout, r)

	server := http.Server{
		Handler: loggedRouter,
	}

	go func() {
		conn, err := s.ln.Accept()
		if err != nil {
			s.logger.Fatalf("Listener accept failed: %s", err)
		}

		go s.handleRequest(conn)
	}()

	go func() {
		err := server.Serve(s.ln)
		if err != nil {
			s.logger.Fatalf("HTTP serve: %s", err)
		}
	}()

	return nil
}

// Close closes the service.
func (s *Server) Close() {
	s.ln.Close()
	return
}

func (s *Server) handleRequest(conn net.Conn) {
	defer conn.Close()

	header := new(protocol.RequestHeader)
	p := make([]byte, 4)

	for {
		err := conn.SetReadDeadline(time.Now().Add(5 * time.Second))
		if err != nil {
			s.logger.Printf("read deadline failed: %s", err)
			continue
		}

		n, err := io.ReadFull(conn, p[:])
		if err == io.EOF {
			break
		}
		if n == 0 || err != nil {
			// TODO: handle err
			s.logger.Printf("Conn read failed: %s", err)
			continue
		}

		size := protocol.Encoding.Uint32(p)
		if size == 0 {
			continue
		}
		b := make([]byte, size+4) //+4 since we're going to copy the size into b
		copy(b, p)

		_, err = io.ReadFull(conn, b[4:])
		if err != nil {
			panic(err)
		}

		d := protocol.NewDecoder(b)
		header.Decode(d)

		switch header.APIKey {
		case 0:
			req := &protocol.ProduceRequest{}
			req.Decode(d)
			if err = s.handleProduce(conn, header, req); err != nil {
				s.logger.Printf("produce failed: %s", err)
			}
		case 1:
			req := &protocol.FetchRequest{}
			req.Decode(d)
			if err = s.handleFetch(conn, header, req); err != nil {
				s.logger.Printf("fetch failed: %s", err)
			}
		case 3:
			req := &protocol.MetadataRequest{}
			req.Decode(d)
			if err = s.handleMetadata(conn, header, req); err != nil {
				s.logger.Printf("metadata failed: %s", err)
			}
		case 19:
			req := &protocol.CreateTopicRequests{}
			req.Decode(d)
			if err = s.handleCreateTopic(conn, header, req); err != nil {
				s.logger.Printf("create topic failed: %s", err)
			}
		}
	}
}

func (s *Server) handleCreateTopic(conn net.Conn, header *protocol.RequestHeader, reqs *protocol.CreateTopicRequests) (err error) {
	resp := new(protocol.CreateTopicsResponse)
	resp.TopicErrorCodes = make([]*protocol.TopicErrorCode, len(reqs.Requests))

	if s.broker.IsController() {
		for i, req := range reqs.Requests {
			err = s.broker.CreateTopic(req.Topic, req.NumPartitions)
			if err != nil {
				s.logger.Printf("[ERR] jocko: Failed to create topic %s: %v", req.Topic, err)
				return
			}
			resp.TopicErrorCodes[i] = &protocol.TopicErrorCode{
				Topic:     req.Topic,
				ErrorCode: protocol.ErrNone,
			}
		}
	} else {
		// cID := s.broker.ControllerID()
		// send the request to the controller
		return
	}
	r := &protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	}

	b, err := protocol.Encode(r)

	if err != nil {
		return err
	}

	_, err = conn.Write(b)

	return err
}

func zero(p []byte) {
	for i := range p {
		p[i] = 0
	}
}

func (s *Server) handleJoin(w http.ResponseWriter, r *http.Request) {
	m := map[string]string{}
	if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	if len(m) != 1 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	remoteAddr, ok := m["addr"]
	if !ok {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	if err := s.broker.Join(remoteAddr); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}

func (s *Server) handleMetadata(conn net.Conn, header *protocol.RequestHeader, req *protocol.MetadataRequest) error {
	brokerIDs, err := s.broker.Brokers()
	brokers := make([]*protocol.Broker, len(brokerIDs))
	topics := make([]*protocol.TopicMetadata, len(req.Topics))
	for i, bID := range brokerIDs {
		host, port, err := net.SplitHostPort(bID)
		if err != nil {
			return err
		}
		// TODO: replace this with an actual id
		nodeID := i
		if err != nil {
			return err
		}
		nPort, err := strconv.Atoi(port)
		if err != nil {
			return err
		}
		brokers[i] = &protocol.Broker{
			NodeID: int32(nodeID),
			Host:   host,
			Port:   int32(nPort),
		}
	}
	for i, t := range req.Topics {
		partitions, err := s.broker.PartitionsForTopic(t)
		if err != nil {
			return err
		}
		partitionMetadata := make([]*protocol.PartitionMetadata, len(partitions))
		for i, p := range partitions {
			partitionMetadata[i] = &protocol.PartitionMetadata{
				ParititionID: p.Partition,
			}
		}
		topics[i] = &protocol.TopicMetadata{
			TopicErrorCode:    protocol.ErrNone,
			Topic:             t,
			PartitionMetadata: partitionMetadata,
		}
	}
	resp := &protocol.MetadataResponse{
		Brokers:       brokers,
		TopicMetadata: topics,
	}
	r := &protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	}
	b, err := protocol.Encode(r)
	if err != nil {
		return err
	}
	_, err = conn.Write(b)
	return err
}

func (s *Server) handleProduce(conn net.Conn, header *protocol.RequestHeader, r *protocol.ProduceRequest) error {
	resp := new(protocol.ProduceResponses)
	resp.Responses = make([]*protocol.ProduceResponse, len(r.TopicData))
	for i, td := range r.TopicData {
		presps := make([]*protocol.ProducePartitionresponse, len(td.Data))
		for j, p := range td.Data {
			partition := &cluster.TopicPartition{
				Topic:     td.Topic,
				Partition: p.Partition,
			}
			presp := &protocol.ProducePartitionresponse{}
			partition, err := s.broker.Partition(td.Topic, p.Partition)
			if err != nil {
				presp.ErrorCode = protocol.ErrUnknownTopicOrPartition
			}
			if !s.broker.IsLeaderOfPartition(partition) {
				presp.ErrorCode = protocol.ErrNotLeaderForPartition
			}
			err = partition.CommitLog.Append(p.RecordSet)
			if err != nil {
				s.logger.Printf("commitlog append failed: %s", err)
				presp.ErrorCode = protocol.ErrUnknown
			}
			presp.Partition = p.Partition
			// TODO: presp.BaseOffset
			presp.Timestamp = time.Now().Unix()
			presps[j] = presp
		}
		resp.Responses[i] = &protocol.ProduceResponse{
			Topic:              td.Topic,
			PartitionResponses: presps,
		}
	}

	b, err := protocol.Encode(&protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	})
	if err != nil {
		return err
	}
	_, err = conn.Write(b)
	return err
}

func (s *Server) handleFetch(conn net.Conn, header *protocol.RequestHeader, r *protocol.FetchRequest) error {
	resp := &protocol.FetchResponses{}
	resp.Responses = make([]*protocol.FetchResponse, len(r.Topics))
	received := time.Now()
	for i, topic := range r.Topics {
		fr := &protocol.FetchResponse{}
		resp.Responses[i] = fr
		fr.PartitionResponses = make([]*protocol.FetchPartitionResponse, len(topic.Partitions))
		for j, p := range topic.Partitions {
			partition, err := s.broker.Partition(topic.Topic, p.Partition)
			if err != nil {
				// TODO set err code
				s.logger.Printf("[ERR] jocko: Failed to find partition: %v (%s/%d)", err, topic.Topic, p.Partition)
				break
			}
			if !s.broker.IsLeaderOfPartition(partition) {
				s.logger.Printf("[ERR] jocko: Failed to produce: %v", errors.New("broker is not partition leader"))
				// TODO set err code
				break
			}
			rdr, err := partition.CommitLog.NewReader(commitlog.ReaderOptions{
				Offset:   p.FetchOffset,
				MaxBytes: p.MaxBytes,
			})
			if err != nil {
				s.logger.Printf("[ERR] jocko: Failed to read partition: %v", err)
				// TODO set err code
				break
			}
			b := bytes.NewBuffer(make([]byte, 0))
			var n int32
			for n < r.MinBytes {
				if r.MaxWaitTime != 0 && int32(time.Since(received).Nanoseconds()/1e6) > r.MaxWaitTime {
					break
				}

				// TODO: copy these bytes to outer bytes
				nn, err := io.Copy(b, rdr)
				if err != nil && err != io.EOF {
					s.logger.Printf("[ERR] jocko: Failed to fetch messages: %v", err)
					// TODO seT error code
					break
				}
				n += int32(nn)
				if err == io.EOF {
					break
				}
			}
			fr.PartitionResponses[j] = &protocol.FetchPartitionResponse{
				Partition:     p.Partition,
				ErrorCode:     protocol.ErrNone,
				HighWatermark: 0, // TODO get last committed offset
				RecordSet:     b.Bytes(),
			}
		}
	}

	b, err := protocol.Encode(&protocol.Response{
		CorrelationID: header.CorrelationID,
		Body:          resp,
	})
	if err != nil {
		return err
	}
	_, err = conn.Write(b)
	return err
}

// Addr returns the address on which the Server is listening
func (s *Server) Addr() net.Addr {
	return s.ln.Addr()
}

func (s *Server) handleNotFound(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
}
<|endoftext|>