<commit_before>/*
 Copyright 2016 Padduck, LLC

 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 httphandlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"fmt"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/apufferi/common"
	"github.com/pufferpanel/apufferi/config"
	pufferdHttp "github.com/pufferpanel/apufferi/http"
	"github.com/pufferpanel/apufferi/logging"
	"github.com/pufferpanel/pufferd/programs"
)

type oauthCache struct {
	oauthToken string
	serverId   string
	scopes     []string
	expireTime int64
}

var cache = make([]*oauthCache, 20)

func OAuth2Handler(scope string, requireServer bool) gin.HandlerFunc {
	return func(gin *gin.Context) {
		failure := true
		defer func() {
			if failure && !gin.IsAborted() {
				pufferdHttp.Respond(gin).Code(pufferdHttp.UNKNOWN).Fail().Status(500).Message("unknown error")
			}
		}()
		authHeader := gin.Request.Header.Get("Authorization")
		var authToken string
		if authHeader == "" {
			authToken = gin.Query("accessToken")
			if authToken == "" {
				pufferdHttp.Respond(gin).Fail().Code(pufferdHttp.NOTAUTHORIZED).Status(400).Message("no access token provided")
				gin.Abort()
				return
			}
		} else {
			authArr := strings.SplitN(authHeader, " ", 2)
			if len(authArr) < 2 || authArr[0] != "Bearer" {
				pufferdHttp.Respond(gin).Code(pufferdHttp.NOTAUTHORIZED).Fail().Status(400).Message("invalid access token format")
				gin.Abort()
				return
			}
			authToken = authArr[1]
		}

		cached := isCachedRequest(authToken)

		if cached != nil {
			gin.Set("server_id", cached.serverId)
			gin.Set("scopes", cached.scopes)
		} else {
			if !validateToken(authToken, gin) {
				return
			}
		}

		rawScopes, _ := gin.Get("scopes")

		if scope != "" {
			scopes := rawScopes.([]string)
			if !common.ContainsValue(scopes, scope) {
				pufferdHttp.Respond(gin).Fail().Status(403).Code(pufferdHttp.NOTAUTHORIZED).Message("missing scope " + scope).Send()
				gin.Abort()
				return
			}
		}

		if requireServer {
			serverId := gin.Param("id")
			canAccessId, _ := gin.Get("server_id")

			accessId := canAccessId.(string)

			var program programs.Program

			if accessId == "*" {
				program, _ = programs.Get(serverId)
			} else {
				program, _ = programs.Get(accessId)
			}

			if program == nil {
				pufferdHttp.Respond(gin).Fail().Status(404).Code(pufferdHttp.NOSERVER).Message("no server with id " + serverId).Send()
				gin.Abort()
				return
			}

			if accessId != program.Id() && accessId != "*" {
				pufferdHttp.Respond(gin).Fail().Status(403).Code(pufferdHttp.NOTAUTHORIZED).Message("invalid server access").Send()
				gin.Abort()
				return
			}

			gin.Set("server", program)
		}
		failure = false
	}
}

func validateToken(accessToken string, gin *gin.Context) bool {
	authUrl := config.Get("infoserver")
	token := config.Get("authtoken")
	client := &http.Client{}
	data := url.Values{}
	data.Set("token", accessToken)
	request, _ := http.NewRequest("POST", authUrl, bytes.NewBufferString(data.Encode()))
	request.Header.Add("Authorization", "Bearer "+token)
	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	request.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
	response, err := client.Do(request)
	defer response.Body.Close()
	if err != nil {
		logging.Error("Error talking to auth server", err)
		errMsg := make(map[string]string)
		errMsg["error"] = err.Error()
		gin.JSON(500, errMsg)
		return false
	}
	if response.StatusCode != 200 {
		logging.Error("Unexpected response code from auth server", response.StatusCode)
		errMsg := make(map[string]string)
		errMsg["error"] = fmt.Sprintf("Received response %i", response.StatusCode)
		gin.JSON(500, errMsg)
		return false
	}
	var respArr map[string]interface{}
	json.NewDecoder(response.Body).Decode(&respArr)
	logging.Debugf("%+v", respArr)
	if respArr["error"] != nil {
		logging.Error("Error parsing response from auth server", err)
		errMsg := make(map[string]string)
		errMsg["error"] = "Failed to parse auth server response"
		gin.JSON(500, errMsg)
		return false
	}
	if respArr["active"].(bool) == false {
		gin.AbortWithStatus(401)
		return false
	}

	serverId := respArr["server_id"].(string)
	scopes := strings.Split(respArr["scope"].(string), " ")

	cache := &oauthCache{
		oauthToken: accessToken,
		serverId:   serverId,
		scopes:     scopes,
	}
	cacheRequest(cache)

	gin.Set("server_id", serverId)
	gin.Set("scopes", scopes)
	return true
}

func isCachedRequest(accessToken string) *oauthCache {
	currentTime := time.Now().Unix()
	for k, v := range cache {
		if v == nil {
			continue
		}
		if v.oauthToken == accessToken {
			if v.expireTime < currentTime {
				return v
			}
			copy(cache[k:], cache[k+1:])
			cache[len(cache)-1] = nil
			cache = cache[:len(cache)-1]
			return nil
		}
	}
	return nil
}

func cacheRequest(request *oauthCache) {
	currentTime := time.Now().Unix()
	request.expireTime = time.Now().Add(time.Minute * 2).Unix()
	for k, v := range cache {
		if v == nil || v.expireTime > currentTime {
			cache[k] = request
			return
		}
	}
}
<commit_msg>Abort when failed<commit_after>/*
 Copyright 2016 Padduck, LLC

 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 httphandlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"fmt"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pufferpanel/apufferi/common"
	"github.com/pufferpanel/apufferi/config"
	pufferdHttp "github.com/pufferpanel/apufferi/http"
	"github.com/pufferpanel/apufferi/logging"
	"github.com/pufferpanel/pufferd/programs"
)

type oauthCache struct {
	oauthToken string
	serverId   string
	scopes     []string
	expireTime int64
}

var cache = make([]*oauthCache, 20)

func OAuth2Handler(scope string, requireServer bool) gin.HandlerFunc {
	return func(gin *gin.Context) {
		failure := true
		defer func() {
			if failure && !gin.IsAborted() {
				pufferdHttp.Respond(gin).Code(pufferdHttp.UNKNOWN).Fail().Status(500).Message("unknown error")
				gin.Abort()
			}
		}()
		authHeader := gin.Request.Header.Get("Authorization")
		var authToken string
		if authHeader == "" {
			authToken = gin.Query("accessToken")
			if authToken == "" {
				pufferdHttp.Respond(gin).Fail().Code(pufferdHttp.NOTAUTHORIZED).Status(400).Message("no access token provided")
				gin.Abort()
				return
			}
		} else {
			authArr := strings.SplitN(authHeader, " ", 2)
			if len(authArr) < 2 || authArr[0] != "Bearer" {
				pufferdHttp.Respond(gin).Code(pufferdHttp.NOTAUTHORIZED).Fail().Status(400).Message("invalid access token format")
				gin.Abort()
				return
			}
			authToken = authArr[1]
		}

		cached := isCachedRequest(authToken)

		if cached != nil {
			gin.Set("server_id", cached.serverId)
			gin.Set("scopes", cached.scopes)
		} else {
			if !validateToken(authToken, gin) {
				return
			}
		}

		rawScopes, _ := gin.Get("scopes")

		if scope != "" {
			scopes := rawScopes.([]string)
			if !common.ContainsValue(scopes, scope) {
				pufferdHttp.Respond(gin).Fail().Status(403).Code(pufferdHttp.NOTAUTHORIZED).Message("missing scope " + scope).Send()
				gin.Abort()
				return
			}
		}

		if requireServer {
			serverId := gin.Param("id")
			canAccessId, _ := gin.Get("server_id")

			accessId := canAccessId.(string)

			var program programs.Program

			if accessId == "*" {
				program, _ = programs.Get(serverId)
			} else {
				program, _ = programs.Get(accessId)
			}

			if program == nil {
				pufferdHttp.Respond(gin).Fail().Status(404).Code(pufferdHttp.NOSERVER).Message("no server with id " + serverId).Send()
				gin.Abort()
				return
			}

			if accessId != program.Id() && accessId != "*" {
				pufferdHttp.Respond(gin).Fail().Status(403).Code(pufferdHttp.NOTAUTHORIZED).Message("invalid server access").Send()
				gin.Abort()
				return
			}

			gin.Set("server", program)
		}
		failure = false
	}
}

func validateToken(accessToken string, gin *gin.Context) bool {
	authUrl := config.Get("infoserver")
	token := config.Get("authtoken")
	client := &http.Client{}
	data := url.Values{}
	data.Set("token", accessToken)
	request, _ := http.NewRequest("POST", authUrl, bytes.NewBufferString(data.Encode()))
	request.Header.Add("Authorization", "Bearer "+token)
	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	request.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
	response, err := client.Do(request)
	defer response.Body.Close()
	if err != nil {
		logging.Error("Error talking to auth server", err)
		errMsg := make(map[string]string)
		errMsg["error"] = err.Error()
		gin.JSON(500, errMsg)
		return false
	}
	if response.StatusCode != 200 {
		logging.Error("Unexpected response code from auth server", response.StatusCode)
		errMsg := make(map[string]string)
		errMsg["error"] = fmt.Sprintf("Received response %i", response.StatusCode)
		gin.JSON(500, errMsg)
		return false
	}
	var respArr map[string]interface{}
	json.NewDecoder(response.Body).Decode(&respArr)
	logging.Debugf("%+v", respArr)
	if respArr["error"] != nil {
		logging.Error("Error parsing response from auth server", err)
		errMsg := make(map[string]string)
		errMsg["error"] = "Failed to parse auth server response"
		gin.JSON(500, errMsg)
		return false
	}
	if respArr["active"].(bool) == false {
		gin.AbortWithStatus(401)
		return false
	}

	serverId := respArr["server_id"].(string)
	scopes := strings.Split(respArr["scope"].(string), " ")

	cache := &oauthCache{
		oauthToken: accessToken,
		serverId:   serverId,
		scopes:     scopes,
	}
	cacheRequest(cache)

	gin.Set("server_id", serverId)
	gin.Set("scopes", scopes)
	return true
}

func isCachedRequest(accessToken string) *oauthCache {
	currentTime := time.Now().Unix()
	for k, v := range cache {
		if v == nil {
			continue
		}
		if v.oauthToken == accessToken {
			if v.expireTime < currentTime {
				return v
			}
			copy(cache[k:], cache[k+1:])
			cache[len(cache)-1] = nil
			cache = cache[:len(cache)-1]
			return nil
		}
	}
	return nil
}

func cacheRequest(request *oauthCache) {
	currentTime := time.Now().Unix()
	request.expireTime = time.Now().Add(time.Minute * 2).Unix()
	for k, v := range cache {
		if v == nil || v.expireTime > currentTime {
			cache[k] = request
			return
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC All Rights Reserved.
//
// 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 sdk

import (
	"context"

	"github.com/pkg/errors"

	"google.golang.org/grpc"

	"agones.dev/agones/pkg/sdk/alpha"
)

// Alpha is the struct for Alpha SDK functionality.
type Alpha struct {
	client alpha.SDKClient
}

// newAlpha creates a new Alpha SDK with the passed in connection.
func newAlpha(conn *grpc.ClientConn) *Alpha {
	return &Alpha{
		client: alpha.NewSDKClient(conn),
	}
}

// GetPlayerCapacity gets the last player capacity that was set through the SDK.
// If the player capacity is set from outside the SDK, use SDK.GameServer() instead.
func (a *Alpha) GetPlayerCapacity() (int64, error) {
	c, err := a.client.GetPlayerCapacity(context.Background(), &alpha.Empty{})
	return c.Count, errors.Wrap(err, "could not get player capacity")
}

// SetPlayerCapacity changes the player capacity to a new value.
func (a *Alpha) SetPlayerCapacity(capacity int64) error {
	_, err := a.client.SetPlayerCapacity(context.Background(), &alpha.Count{Count: capacity})
	return errors.Wrap(err, "could not set player capacity")
}

// PlayerConnect increases the SDK’s stored player count by one, and appends this playerID to status.players.id.
// Will return true and add the playerID to the list of playerIDs if the playerIDs was not already in the
// list of connected playerIDs.
func (a *Alpha) PlayerConnect(id string) (bool, error) {
	ok, err := a.client.PlayerConnect(context.Background(), &alpha.PlayerID{PlayerID: id})
	return ok.Bool, errors.Wrap(err, "could not register connected player")
}

// PlayerDisconnect Decreases the SDK’s stored player count by one, and removes the playerID from status.players.id.
// Will return true and remove the supplied playerID from the list of connected playerIDs if the
// playerID value exists within the list.
func (a *Alpha) PlayerDisconnect(id string) (bool, error) {
	ok, err := a.client.PlayerDisconnect(context.Background(), &alpha.PlayerID{PlayerID: id})
	return ok.Bool, errors.Wrap(err, "could not register disconnected player")
}

// GetPlayerCount returns the current player count.
func (a *Alpha) GetPlayerCount() (int64, error) {
	count, err := a.client.GetPlayerCount(context.Background(), &alpha.Empty{})
	return count.Count, errors.Wrap(err, "could not get player count")
}

// IsPlayerConnected returns if the playerID is currently connected to the GameServer.
// This is always accurate, even if the value hasn’t been updated to the GameServer status yet.
func (a *Alpha) IsPlayerConnected(id string) (bool, error) {
	ok, err := a.client.IsPlayerConnected(context.Background(), &alpha.PlayerID{PlayerID: id})
	return ok.Bool, errors.Wrap(err, "could not get if player is connected")
}

// GetConnectedPlayers returns the list of the currently connected player ids.
// This is always accurate, even if the value hasn’t been updated to the GameServer status yet.
func (a *Alpha) GetConnectedPlayers() ([]string, error) {
	list, err := a.client.GetConnectedPlayers(context.Background(), &alpha.Empty{})
	return list.List, errors.Wrap(err, "could not list connected players")
}
<commit_msg>Fix panic when playertracking is false (#2489)<commit_after>// Copyright 2020 Google LLC All Rights Reserved.
//
// 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 sdk

import (
	"context"

	"github.com/pkg/errors"

	"google.golang.org/grpc"

	"agones.dev/agones/pkg/sdk/alpha"
)

// Alpha is the struct for Alpha SDK functionality.
type Alpha struct {
	client alpha.SDKClient
}

// newAlpha creates a new Alpha SDK with the passed in connection.
func newAlpha(conn *grpc.ClientConn) *Alpha {
	return &Alpha{
		client: alpha.NewSDKClient(conn),
	}
}

// GetPlayerCapacity gets the last player capacity that was set through the SDK.
// If the player capacity is set from outside the SDK, use SDK.GameServer() instead.
func (a *Alpha) GetPlayerCapacity() (int64, error) {
	c, err := a.client.GetPlayerCapacity(context.Background(), &alpha.Empty{})
	return c.GetCount(), errors.Wrap(err, "could not get player capacity")
}

// SetPlayerCapacity changes the player capacity to a new value.
func (a *Alpha) SetPlayerCapacity(capacity int64) error {
	_, err := a.client.SetPlayerCapacity(context.Background(), &alpha.Count{Count: capacity})
	return errors.Wrap(err, "could not set player capacity")
}

// PlayerConnect increases the SDK’s stored player count by one, and appends this playerID to status.players.id.
// Will return true and add the playerID to the list of playerIDs if the playerIDs was not already in the
// list of connected playerIDs.
func (a *Alpha) PlayerConnect(id string) (bool, error) {
	ok, err := a.client.PlayerConnect(context.Background(), &alpha.PlayerID{PlayerID: id})
	return ok.GetBool(), errors.Wrap(err, "could not register connected player")
}

// PlayerDisconnect Decreases the SDK’s stored player count by one, and removes the playerID from status.players.id.
// Will return true and remove the supplied playerID from the list of connected playerIDs if the
// playerID value exists within the list.
func (a *Alpha) PlayerDisconnect(id string) (bool, error) {
	ok, err := a.client.PlayerDisconnect(context.Background(), &alpha.PlayerID{PlayerID: id})
	return ok.GetBool(), errors.Wrap(err, "could not register disconnected player")
}

// GetPlayerCount returns the current player count.
func (a *Alpha) GetPlayerCount() (int64, error) {
	count, err := a.client.GetPlayerCount(context.Background(), &alpha.Empty{})
	return count.GetCount(), errors.Wrap(err, "could not get player count")
}

// IsPlayerConnected returns if the playerID is currently connected to the GameServer.
// This is always accurate, even if the value hasn’t been updated to the GameServer status yet.
func (a *Alpha) IsPlayerConnected(id string) (bool, error) {
	ok, err := a.client.IsPlayerConnected(context.Background(), &alpha.PlayerID{PlayerID: id})
	return ok.GetBool(), errors.Wrap(err, "could not get if player is connected")
}

// GetConnectedPlayers returns the list of the currently connected player ids.
// This is always accurate, even if the value hasn’t been updated to the GameServer status yet.
func (a *Alpha) GetConnectedPlayers() ([]string, error) {
	list, err := a.client.GetConnectedPlayers(context.Background(), &alpha.Empty{})
	return list.GetList(), errors.Wrap(err, "could not list connected players")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"reflect"
	"testing"
)

var selectAllTests = []struct {
	src []string
	dst []string
}{
	{
		src: []string{},
		dst: []string{},
	},
	{
		src: []string{"aaa", "bbb", "ccc"},
		dst: []string{"aaa", "bbb", "ccc"},
	},
	{
		src: []string{"a", "bb", "ccc", "dddd", "eeeee"},
		dst: []string{"a", "bb", "ccc", "dddd", "eeeee"},
	},
}

func TestSelectAll(t *testing.T) {
	a := NewAll()
	for _, test := range selectAllTests {
		expect := test.dst
		actual, err := a.Select(test.src)
		if err != nil {
			t.Errorf("All.Select(%q) returns %q, want nil",
				test.src, err)
		}
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("All.Select(%q) = %q, want %q",
				test.src, actual, expect)
		}
	}
}

var newIndexesTests = []struct {
	description string
	list        string
	wantErr     bool
	indexes     []int
}{
	{
		list:    "",
		indexes: []int{},
	},
	{
		list:    "1",
		indexes: []int{0},
	},
	{
		list:    "3,1,4",
		indexes: []int{2, 0, 3},
	},
	{
		list:    "0,5",
		wantErr: true,
	},
	{
		list:    "-8,5",
		wantErr: true,
	},
	{
		list:    "foo,5",
		wantErr: true,
	},
	{
		list:    "1\\,5",
		wantErr: true,
	},
}

func TestNewIndexes(t *testing.T) {
	for _, test := range newIndexesTests {
		switch {
		case test.wantErr:
			_, err := NewIndexes(test.list)
			if err == nil {
				t.Errorf("NewIndexes(%q) returns nil, want err",
					test.list)
			}
		default:
			i, err := NewIndexes(test.list)
			if err != nil {
				t.Errorf("NewIndexes(%q) returns %q, want nil",
					test.list, err)
				continue
			}
			expect := test.indexes
			actual := i.indexes
			if !reflect.DeepEqual(actual, expect) {
				t.Errorf("NewIndexes(%q) = %v, want %v",
					test.list, actual, expect)
			}
		}
	}
}

var selectIndexesTests = []struct {
	description string
	list        string
	src         [][]string
	dst         [][]string
}{
	{
		description: "no input",
		list:        "1",
		src:         [][]string{},
		dst:         [][]string{},
	},
	{
		description: "only one index",
		list:        "1",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{"aaa"},
			{"ddd"},
		},
	},
	{
		description: "index out of bounds",
		list:        "4",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{""},
			{""},
		},
	},
	{
		description: "multiple indexes",
		list:        "3,1",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{"ccc", "aaa"},
			{"fff", "ddd"},
		},
	},
	{
		description: "duplicated indexes",
		list:        "2,2,2",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{"bbb", "bbb", "bbb"},
			{"eee", "eee", "eee"},
		},
	},
	{
		description: "battery",
		list:        "8,8,2,1,1,4",
		src: [][]string{
			{"a", "bb", "ccc", "dddd", "eeeee"},
			{"f", "gg", "hhh", "iiii", "jjjjj"},
			{"j", "kk", "lll", "mmmm", "nnnnn"},
		},
		dst: [][]string{
			{"", "", "bb", "a", "a", "dddd"},
			{"", "", "gg", "f", "f", "iiii"},
			{"", "", "kk", "j", "j", "mmmm"},
		},
	},
}

func TestSelectIndexes(t *testing.T) {
	for _, test := range selectIndexesTests {
		i, err := NewIndexes(test.list)
		if err != nil {
			t.Errorf("NewIndexes(%q) returns %q, want nil",
				test.list, err)
		}
		self := fmt.Sprintf("{list=%q, description=%q}",
			test.list, test.description)

		expect := test.dst
		actual := make([][]string, len(test.src))
		for j, line := range test.src {
			actual[j], err = i.Select(line)
			if err != nil {
				t.Errorf("%s: Select(%q) returns %q, want nil",
					self, line, err)
			}
		}
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("%s: %q: got %q, want %q",
				self, test.src, actual, expect)
		}
	}
}

var newHeadersTests = []struct {
	list    string
	headers []string
}{
	{
		list:    "",
		headers: []string{},
	},
	{
		list:    "name",
		headers: []string{"name"},
	},
	{
		list:    "name,price,quantity",
		headers: []string{"name", "price", "quantity"},
	},
	{
		list:    "a\\,b\\,c,d\\,e\\,f",
		headers: []string{"a,b,c", "d,e,f"},
	},
}

func TestNewHeaders(t *testing.T) {
	for _, test := range newHeadersTests {
		h, err := NewHeaders(test.list)
		if err != nil {
			t.Errorf("NewHeaders(%q) returns %q, want nil",
				test.list, err)
			continue
		}
		expect := test.headers
		actual := h.headers
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("NewHeaders(%q) = %v, want %v",
				test.list, actual, expect)
		}
	}
}

var headersParseHeadersTests = []struct {
	list    string
	headers []string
	indexes []int
}{
	{
		list:    "",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{},
	},
	{
		list:    "name",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{0},
	},
	{
		list:    "price,name",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{1, 0},
	},
	{
		list:    "quantity,quantity",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{2, 2},
	},
	{
		list:    "date,name",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{-1, 0},
	},
	{
		list:    "date,name,name,quantity,per,per",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{-1, 0, 0, 2, -1, -1},
	},
}

func TestHeadersParseHeaders(t *testing.T) {
	for _, test := range headersParseHeadersTests {
		h, err := NewHeaders(test.list)
		if err != nil {
			t.Errorf("NewHeaders(%q) returns %q, want nil",
				test.list, err)
			continue
		}
		if err = h.ParseHeaders(test.headers); err != nil {
			t.Errorf("%q.ParseHeaders(%q) returns %q, want nil",
				test.list, test.headers, err)
			continue
		}

		expect := test.indexes
		actual := h.indexes
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("%q.ParseHeaders(%q).indexes = %v, want %v",
				test.list, test.indexes, actual, expect)
		}
	}
}
<commit_msg>Add test for Headers.Select<commit_after>package main

import (
	"fmt"
	"reflect"
	"testing"
)

var selectAllTests = []struct {
	src []string
	dst []string
}{
	{
		src: []string{},
		dst: []string{},
	},
	{
		src: []string{"aaa", "bbb", "ccc"},
		dst: []string{"aaa", "bbb", "ccc"},
	},
	{
		src: []string{"a", "bb", "ccc", "dddd", "eeeee"},
		dst: []string{"a", "bb", "ccc", "dddd", "eeeee"},
	},
}

func TestSelectAll(t *testing.T) {
	a := NewAll()
	for _, test := range selectAllTests {
		expect := test.dst
		actual, err := a.Select(test.src)
		if err != nil {
			t.Errorf("All.Select(%q) returns %q, want nil",
				test.src, err)
		}
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("All.Select(%q) = %q, want %q",
				test.src, actual, expect)
		}
	}
}

var newIndexesTests = []struct {
	description string
	list        string
	wantErr     bool
	indexes     []int
}{
	{
		list:    "",
		indexes: []int{},
	},
	{
		list:    "1",
		indexes: []int{0},
	},
	{
		list:    "3,1,4",
		indexes: []int{2, 0, 3},
	},
	{
		list:    "0,5",
		wantErr: true,
	},
	{
		list:    "-8,5",
		wantErr: true,
	},
	{
		list:    "foo,5",
		wantErr: true,
	},
	{
		list:    "1\\,5",
		wantErr: true,
	},
}

func TestNewIndexes(t *testing.T) {
	for _, test := range newIndexesTests {
		switch {
		case test.wantErr:
			_, err := NewIndexes(test.list)
			if err == nil {
				t.Errorf("NewIndexes(%q) returns nil, want err",
					test.list)
			}
		default:
			i, err := NewIndexes(test.list)
			if err != nil {
				t.Errorf("NewIndexes(%q) returns %q, want nil",
					test.list, err)
				continue
			}
			expect := test.indexes
			actual := i.indexes
			if !reflect.DeepEqual(actual, expect) {
				t.Errorf("NewIndexes(%q) = %v, want %v",
					test.list, actual, expect)
			}
		}
	}
}

var selectIndexesTests = []struct {
	description string
	list        string
	src         [][]string
	dst         [][]string
}{
	{
		description: "no input",
		list:        "1",
		src:         [][]string{},
		dst:         [][]string{},
	},
	{
		description: "only one index",
		list:        "1",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{"aaa"},
			{"ddd"},
		},
	},
	{
		description: "index out of bounds",
		list:        "4",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{""},
			{""},
		},
	},
	{
		description: "multiple indexes",
		list:        "3,1",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{"ccc", "aaa"},
			{"fff", "ddd"},
		},
	},
	{
		description: "duplicated indexes",
		list:        "2,2,2",
		src: [][]string{
			{"aaa", "bbb", "ccc"},
			{"ddd", "eee", "fff"},
		},
		dst: [][]string{
			{"bbb", "bbb", "bbb"},
			{"eee", "eee", "eee"},
		},
	},
	{
		description: "battery",
		list:        "8,8,2,1,1,4",
		src: [][]string{
			{"a", "bb", "ccc", "dddd", "eeeee"},
			{"f", "gg", "hhh", "iiii", "jjjjj"},
			{"j", "kk", "lll", "mmmm", "nnnnn"},
		},
		dst: [][]string{
			{"", "", "bb", "a", "a", "dddd"},
			{"", "", "gg", "f", "f", "iiii"},
			{"", "", "kk", "j", "j", "mmmm"},
		},
	},
}

func TestSelectIndexes(t *testing.T) {
	for _, test := range selectIndexesTests {
		i, err := NewIndexes(test.list)
		if err != nil {
			t.Errorf("NewIndexes(%q) returns %q, want nil",
				test.list, err)
		}
		self := fmt.Sprintf("{list=%q, description=%q}",
			test.list, test.description)

		expect := test.dst
		actual := make([][]string, len(test.src))
		for j, line := range test.src {
			actual[j], err = i.Select(line)
			if err != nil {
				t.Errorf("%s: Select(%q) returns %q, want nil",
					self, line, err)
			}
		}
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("%s: %q: got %q, want %q",
				self, test.src, actual, expect)
		}
	}
}

var newHeadersTests = []struct {
	list    string
	headers []string
}{
	{
		list:    "",
		headers: []string{},
	},
	{
		list:    "name",
		headers: []string{"name"},
	},
	{
		list:    "name,price,quantity",
		headers: []string{"name", "price", "quantity"},
	},
	{
		list:    "a\\,b\\,c,d\\,e\\,f",
		headers: []string{"a,b,c", "d,e,f"},
	},
}

func TestNewHeaders(t *testing.T) {
	for _, test := range newHeadersTests {
		h, err := NewHeaders(test.list)
		if err != nil {
			t.Errorf("NewHeaders(%q) returns %q, want nil",
				test.list, err)
			continue
		}
		expect := test.headers
		actual := h.headers
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("NewHeaders(%q) = %v, want %v",
				test.list, actual, expect)
		}
	}
}

var headersParseHeadersTests = []struct {
	list    string
	headers []string
	indexes []int
}{
	{
		list:    "",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{},
	},
	{
		list:    "name",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{0},
	},
	{
		list:    "price,name",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{1, 0},
	},
	{
		list:    "quantity,quantity",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{2, 2},
	},
	{
		list:    "date,name",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{-1, 0},
	},
	{
		list:    "date,name,name,quantity,per,per",
		headers: []string{"name", "price", "quantity"},
		indexes: []int{-1, 0, 0, 2, -1, -1},
	},
}

func TestHeadersParseHeaders(t *testing.T) {
	for _, test := range headersParseHeadersTests {
		h, err := NewHeaders(test.list)
		if err != nil {
			t.Errorf("NewHeaders(%q) returns %q, want nil",
				test.list, err)
			continue
		}
		if err = h.ParseHeaders(test.headers); err != nil {
			t.Errorf("%q.ParseHeaders(%q) returns %q, want nil",
				test.list, test.headers, err)
			continue
		}

		expect := test.indexes
		actual := h.indexes
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("%q.ParseHeaders(%q).indexes = %v, want %v",
				test.list, test.indexes, actual, expect)
		}
	}
}

var selectHeadersTests = []struct {
	list    string
	headers []string
	src     [][]string
	dst     [][]string
}{
	{
		list:    "",
		headers: []string{"name", "price", "quantity"},
		src: [][]string{
			{"Apple", "60", "20"},
			{"Grapes", "140", "8"},
			{"Pineapple", "400", "2"},
			{"Orange", "50", "14"},
		},
		dst: [][]string{
			{},
			{},
			{},
			{},
		},
	},
	{
		list:    "name",
		headers: []string{"name", "price", "quantity"},
		src: [][]string{
			{"Apple", "60", "20"},
			{"Grapes", "140", "8"},
			{"Pineapple", "400", "2"},
			{"Orange", "50", "14"},
		},
		dst: [][]string{
			{"Apple"},
			{"Grapes"},
			{"Pineapple"},
			{"Orange"},
		},
	},
	{
		list:    "price,name",
		headers: []string{"name", "price", "quantity"},
		src: [][]string{
			{"Apple", "60", "20"},
			{"Grapes", "140", "8"},
			{"Pineapple", "400", "2"},
			{"Orange", "50", "14"},
		},
		dst: [][]string{
			{"60", "Apple"},
			{"140", "Grapes"},
			{"400", "Pineapple"},
			{"50", "Orange"},
		},
	},
	{
		list:    "quantity,quantity",
		headers: []string{"name", "price", "quantity"},
		src: [][]string{
			{"Apple", "60", "20"},
			{"Grapes", "140", "8"},
			{"Pineapple", "400", "2"},
			{"Orange", "50", "14"},
		},
		dst: [][]string{
			{"20", "20"},
			{"8", "8"},
			{"2", "2"},
			{"14", "14"},
		},
	},
	{
		list:    "date,name",
		headers: []string{"name", "price", "quantity"},
		src: [][]string{
			{"Apple", "60", "20"},
			{"Grapes", "140", "8"},
			{"Pineapple", "400", "2"},
			{"Orange", "50", "14"},
		},
		dst: [][]string{
			{"", "Apple"},
			{"", "Grapes"},
			{"", "Pineapple"},
			{"", "Orange"},
		},
	},
	{
		list:    "date,name,name,quantity,per,per",
		headers: []string{"name", "price", "quantity"},
		src: [][]string{
			{"Apple", "60", "20"},
			{"Grapes", "140", "8"},
			{"Pineapple", "400", "2"},
			{"Orange", "50", "14"},
		},
		dst: [][]string{
			{"", "Apple", "Apple", "20", "", ""},
			{"", "Grapes", "Grapes", "8", "", ""},
			{"", "Pineapple", "Pineapple", "2", "", ""},
			{"", "Orange", "Orange", "14", "", ""},
		},
	},
}

func TestSelectHeaders(t *testing.T) {
	for _, test := range selectHeadersTests {
		h, err := NewHeaders(test.list)
		if err != nil {
			t.Errorf("NewHeaders(%q) returns %q, want nil",
				test.list, err)
			continue
		}
		if err = h.ParseHeaders(test.headers); err != nil {
			t.Errorf("%q.ParseHeaders(%q) returns %q, want nil",
				test.list, test.headers, err)
			continue
		}
		self := fmt.Sprintf("{list=%q, headers=%q}",
			test.list, test.headers)

		expect := test.dst
		actual := make([][]string, len(test.src))
		for i, line := range test.src {
			actual[i], err = h.Select(line)
			if err != nil {
				t.Errorf("%s.Select(%q) returns %q, want nil",
					self, line, err)
			}
		}
		if !reflect.DeepEqual(actual, expect) {
			t.Errorf("%s: %q: got %q, want %q",
				self, test.src, actual, expect)
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"fmt"
	"html"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"os"
	"strings"
	"sync"

	"github.com/jaschaephraim/lrserver"
	"github.com/osteele/gojekyll/site"
	"github.com/osteele/liquid"
	"github.com/pkg/browser"
)

// Server serves the site on HTTP.
type Server struct {
	sync.Mutex
	Site *site.Site
	lr   *lrserver.Server
}

// Run runs the server.
func (s *Server) Run(open bool, logger func(label, value string)) error {
	cfg := s.Site.Config()
	s.Site.SetAbsoluteURL("")
	address := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
	logger("Server address:", "http://"+address+"/")
	if cfg.Watch {
		if err := s.startLiveReloader(); err != nil {
			return err
		}
		if err := s.watchReload(); err != nil {
			return err
		}
	}
	http.HandleFunc("/", s.handler)
	c := make(chan error)
	go func() {
		c <- http.ListenAndServe(address, nil)
	}()
	logger("Server running...", "press ctrl-c to stop.")
	if open {
		if err := browser.OpenURL("http://" + address); err != nil {
			fmt.Println("Error opening page:", err)
		}
	}
	return <-c
}

func (s *Server) handler(rw http.ResponseWriter, r *http.Request) {
	s.Lock()
	defer s.Unlock()

	var (
		site     = s.Site
		urlpath  = r.URL.Path
		p, found = site.URLPage(urlpath)
	)
	if !found {
		rw.WriteHeader(http.StatusNotFound)
		p, found = site.Routes["/404.html"]
	}
	if !found {
		fmt.Fprintf(rw, "404 page not found: %s\n", urlpath) // nolint: gas
		return
	}
	mimeType := mime.TypeByExtension(p.OutputExt())
	if mimeType != "" {
		rw.Header().Set("Content-Type", mimeType)
	}
	var w io.Writer = rw
	if strings.HasPrefix(mimeType, "text/html;") {
		w = NewLiveReloadInjector(w)
	}
	err := site.WriteDocument(w, p)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error rendering %s: %s\n", urlpath, err)
		eng := liquid.NewEngine()
		excerpt, path := fileErrorContext(err)
		out, e := eng.ParseAndRenderString(renderErrorTemplate, liquid.Bindings{
			"error":   err,
			"excerpt": excerpt,
			"path":    path,
			"watch":   site.Config().Watch,
		})
		if e != nil {
			panic(e)
		}
		if _, err := io.WriteString(w, out); err != nil {
			fmt.Fprintf(os.Stderr, "Error writing HTTP response: %s", err)
		}
	}
}

func fileErrorContext(e error) (s, path string) {
	cause, ok := e.(liquid.SourceError)
	if !ok {
		return
	}
	path, n := cause.Path(), cause.LineNumber()
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}
	lines := strings.Split(strings.TrimRight(string(b), "\n"), "\n")
	l0, l1 := n-4, n+4
	w := new(bytes.Buffer)
	for i := l0; i < l1; i++ {
		if i < 0 || len(lines) <= i {
			continue
		}
		var class string
		if i+1 == n {
			class = "error"
		}
		fmt.Fprintf(w, `<span class="line %s"><span class="gutter"></span><span class="lineno">%4d</span>%s<br /></span>`, class, i+1, html.EscapeString(lines[i]))
	}
	return w.String(), path
}

// CSS theme adapted from github.com/facebookincubator/create-react-app
const renderErrorTemplate = `

Failed to render.

{{ error }}:

		{{ excerpt }}
		{% if watch and path != "" %}
		Edit and save “{{ path }}” to reload this page.
		{% endif %}

`
<commit_msg>Tweak in-page error display<commit_after>package server

import (
	"bytes"
	"fmt"
	"html"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"os"
	"strings"
	"sync"

	"github.com/jaschaephraim/lrserver"
	"github.com/osteele/gojekyll/site"
	"github.com/osteele/liquid"
	"github.com/pkg/browser"
)

// Server serves the site on HTTP.
type Server struct {
	sync.Mutex
	Site *site.Site
	lr   *lrserver.Server
}

// Run runs the server.
func (s *Server) Run(open bool, logger func(label, value string)) error {
	cfg := s.Site.Config()
	s.Site.SetAbsoluteURL("")
	address := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
	logger("Server address:", "http://"+address+"/")
	if cfg.Watch {
		if err := s.startLiveReloader(); err != nil {
			return err
		}
		if err := s.watchReload(); err != nil {
			return err
		}
	}
	http.HandleFunc("/", s.handler)
	c := make(chan error)
	go func() {
		c <- http.ListenAndServe(address, nil)
	}()
	logger("Server running...", "press ctrl-c to stop.")
	if open {
		if err := browser.OpenURL("http://" + address); err != nil {
			fmt.Println("Error opening page:", err)
		}
	}
	return <-c
}

func (s *Server) handler(rw http.ResponseWriter, r *http.Request) {
	s.Lock()
	defer s.Unlock()

	var (
		site     = s.Site
		urlpath  = r.URL.Path
		p, found = site.URLPage(urlpath)
	)
	if !found {
		rw.WriteHeader(http.StatusNotFound)
		p, found = site.Routes["/404.html"]
	}
	if !found {
		fmt.Fprintf(rw, "404 page not found: %s\n", urlpath) // nolint: gas
		return
	}
	mimeType := mime.TypeByExtension(p.OutputExt())
	if mimeType != "" {
		rw.Header().Set("Content-Type", mimeType)
	}
	var w io.Writer = rw
	if strings.HasPrefix(mimeType, "text/html;") {
		w = NewLiveReloadInjector(w)
	}
	err := site.WriteDocument(w, p)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error rendering %s: %s\n", urlpath, err)
		eng := liquid.NewEngine()
		excerpt, path := fileErrorContext(err)
		out, e := eng.ParseAndRenderString(renderErrorTemplate, liquid.Bindings{
			"error":   fmt.Sprint(err),
			"excerpt": excerpt,
			"path":    path,
			"watch":   site.Config().Watch,
		})
		if e != nil {
			panic(e)
		}
		if _, err := io.WriteString(w, out); err != nil {
			fmt.Fprintf(os.Stderr, "Error writing HTTP response: %s", err)
		}
	}
}

func fileErrorContext(e error) (s, path string) {
	cause, ok := e.(liquid.SourceError)
	if !ok {
		return
	}
	path, n := cause.Path(), cause.LineNumber()
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}
	lines := strings.Split(strings.TrimRight(string(b), "\n"), "\n")
	l0, l1 := n-4, n+4
	w := new(bytes.Buffer)
	for i := l0; i < l1; i++ {
		if i < 0 || len(lines) <= i {
			continue
		}
		var class string
		if i+1 == n {
			class = "error"
		}
		fmt.Fprintf(w, `<span class="line %s"><span class="gutter"></span><span class="lineno">%4d</span>%s<br /></span>`, class, i+1, html.EscapeString(lines[i]))
	}
	return w.String(), path
}

// CSS theme adapted from github.com/facebookincubator/create-react-app
const renderErrorTemplate = `

Failed to render.

{{ error }}:

		{{ excerpt }}
		{% if watch and path != "" %}
		Edit and save “{{ path }}” to reload this page.
		{% endif %}

`
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"io/ioutil"
	"log"
	"mime"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/jaschaephraim/lrserver"
	"github.com/jpillora/archive"
	"github.com/jpillora/sizestr"
	"gopkg.in/fsnotify.v1"
)

//Server is custom file server
type Server struct {
	c            Config
	addr         string
	port         string
	root         string
	colors       *colors
	hasIndex     bool
	fallback     *httputil.ReverseProxy
	fallbackHost string
	watcher      *fsnotify.Watcher
	watching     map[string]bool
	lr           *lrserver.Server
}

//NewServer creates a new Server
func New(c Config) (*Server, error) {

	port := strconv.Itoa(c.Port)
	s := &Server{
		c:    c,
		port: port,
		addr: c.Host + ":" + port,
	}

	_, err := os.Stat(c.Directory)
	if c.Directory == "" || err != nil {
		return nil, fmt.Errorf("Missing directory: %s", c.Directory)
	}

	if c.NoColor {
		s.colors = &colors{}
	} else {
		s.colors = defaultColors
	}

	if c.PushState {
		s.root = filepath.Join(c.Directory, "index.html")
		if _, err := os.Stat(s.root); err != nil {
			return nil, fmt.Errorf("'%s' is required for pushstate", s.root)

		}
		s.hasIndex = true
	}

	if c.Fallback != "" {
		u, err := url.Parse(c.Fallback)
		if err != nil {
			return nil, err
		}
		if !strings.HasPrefix(u.Scheme, "http") {
			return nil, fmt.Errorf("Invalid fallback protocol scheme")
		}
		s.fallbackHost = u.Host
		s.fallback = httputil.NewSingleHostReverseProxy(u)
	}

	if c.LiveReload {
		s.lr, _ = lrserver.New("serve-lr", lrserver.DefaultPort)
		discard := log.New(ioutil.Discard, "", 0)
		s.lr.SetErrorLog(discard)
		s.lr.SetStatusLog(discard)
		s.watching = map[string]bool{}
		s.watcher, err = fsnotify.NewWatcher()
		if err != nil {
			return nil, err
		}
	}

	return s, nil
}

func (s *Server) Start() error {

	if s.c.Open {
		go func() {
			time.Sleep(500 * time.Millisecond)
			cmd := exec.Command("open", "http://localhost:"+s.port)
			cmd.Run()
		}()
	}

	if s.c.LiveReload {
		go func() {
			if err := s.lr.ListenAndServe(); err != nil {
				fmt.Printf("LiveReload server closed: %s", err)
			}
		}()
		go func() {
			for {
				event := <-s.watcher.Events
				s.lr.Reload(event.Name)
			}
		}()
	}

	h := http.Handler(http.HandlerFunc(s.serve))

	//logging is enabled
	if !s.c.Quiet {
		introTemplate.Execute(os.Stdout, &struct {
			*colors
			Dir, Port string
		}{
			s.colors,
			s.c.Directory, s.port,
		})
	}
	//listen
	return http.ListenAndServe(s.addr, h)
}

func (s *Server) serve(w http.ResponseWriter, r *http.Request) {

	//when logs are enabled, swap out response writer with
	//inspectable version
	if !s.c.Quiet {
		sw := &ServeWriter{w: w}
		w = sw
		//track timing
		t0 := time.Now()
		defer func() {
			t := time.Now().Sub(t0)
			//show ip if external
			ip := ""
			h, _, _ := net.SplitHostPort(r.RemoteAddr)
			if h != "127.0.0.1" && h != "::1" {
				ip = h
			}
			cc := ""
			if !s.c.NoColor {
				cc = colorcode(sw.Code)
			}
			logTemplate.Execute(os.Stdout, &struct {
				*colors
				Timestamp, Method, Path, CodeColor string
				Code                               int
				Duration, Size, IP                 string
			}{
				s.colors,
				t0.Format(s.c.TimeFmt), r.Method, r.URL.Path, cc,
				sw.Code,
				fmtduration(t), sizestr.ToString(sw.Size), ip,
			})
		}()
	}

	path := r.URL.Path
	//shorthand
	reply := func(c int, msg string) {
		w.WriteHeader(c)
		if msg != "" {
			w.Write([]byte(msg))
		}
	}
	//requested file
	p := filepath.Join(s.c.Directory, path)
	//check file or dir
	isdir := false
	missing := false
	if info, err := os.Stat(p); err != nil {
		missing = true
	} else {
		isdir = info.IsDir()
	}

	if s.c.PushState && missing && filepath.Ext(p) == "" {
		//missing and pushstate and no ext
		p = s.root //change to request for the root
		isdir = false
		missing = false
	}

	if s.fallback != nil && (missing || isdir) {
		//fallback proxy enabled
		r.Host = s.fallbackHost
		s.fallback.ServeHTTP(w, r)
		return
	}

	if !s.c.NoArchive && missing {
		//check if is archivable
		ok := false
		ext := archive.Extension(p)
		dir := ""
		if ext != "" {
			var err error
			if dir, err = filepath.Abs(strings.TrimSuffix(p, ext)); err == nil {
				if info, err := os.Stat(dir); err == nil && info.IsDir() {
					ok = true
				}
			}
		}
		if ok {
			w.Header().Set("Content-Type", mime.TypeByExtension(ext))
			w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(dir)+ext)
			w.WriteHeader(200)
			//write archive
			a, _ := archive.NewWriter(ext, w)
			if err := a.AddDir(dir); err != nil {
				w.Write([]byte("\n\nERROR: " + err.Error()))
				return
			}
			if err := a.Close(); err != nil {
				w.Write([]byte("\n\nERROR: " + err.Error()))
				return
			}
			return
		}
	}

	if !isdir && missing {
		//file not found!!
		reply(404, "Not found")
		return
	}

	//force trailing slash
	if isdir && !s.c.NoSlash && !strings.HasSuffix(path, "/") {
		w.Header().Set("Location", path+"/")
		w.WriteHeader(302)
		w.Write([]byte("Redirecting (must use slash for directories)"))
		return
	}

	//optionally use index instead of directory list
	if isdir && !s.c.NoIndex {
		dirindex := filepath.Join(p, "index.html")
		if _, err := os.Stat(dirindex); err == nil {
			p = dirindex
			isdir = false
		}
	}

	//directory list
	if isdir {
		if s.c.NoList {
			reply(403, "Listing not allowed")
			return
		}
		s.dirlist(w, r, p)
		return
	}

	//check file again
	info, err := os.Stat(p)
	if err != nil {
		reply(404, "Not found")
		return
	}

	//stream file
	f, err := os.Open(p)
	if err != nil {
		reply(500, err.Error())
		return
	}

	//add all served file's parent dirs to the watcher
	if s.c.LiveReload {
		dir, _ := filepath.Split(p)
		if _, watching := s.watching[dir]; !watching {
			if err := s.watcher.Add(dir); err == nil {
				s.watching[dir] = true
			}
		}
	}
	//http.ServeContent handles caching and range requests
	http.ServeContent(w, r, info.Name(), info.ModTime(), f)
}
<commit_msg>livereload on create and renames (not deletes), reset modtimes on initial files (prevents caching different root dirs)<commit_after>package server

import (
	"fmt"
	"io/ioutil"
	"log"
	"mime"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/jaschaephraim/lrserver"
	"github.com/jpillora/archive"
	"github.com/jpillora/sizestr"
	"gopkg.in/fsnotify.v1"
)

//Server is custom file server
type Server struct {
	c            Config
	addr         string
	port         string
	root         string
	colors       *colors
	hasIndex     bool
	served       map[string]bool
	fallback     *httputil.ReverseProxy
	fallbackHost string
	watcher      *fsnotify.Watcher
	watching     map[string]bool
	lr           *lrserver.Server
}

//NewServer creates a new Server
func New(c Config) (*Server, error) {

	port := strconv.Itoa(c.Port)
	s := &Server{
		c:      c,
		port:   port,
		addr:   c.Host + ":" + port,
		served: map[string]bool{},
	}

	_, err := os.Stat(c.Directory)
	if c.Directory == "" || err != nil {
		return nil, fmt.Errorf("Missing directory: %s", c.Directory)
	}

	if c.NoColor {
		s.colors = &colors{}
	} else {
		s.colors = defaultColors
	}

	if c.PushState {
		s.root = filepath.Join(c.Directory, "index.html")
		if _, err := os.Stat(s.root); err != nil {
			return nil, fmt.Errorf("'%s' is required for pushstate", s.root)

		}
		s.hasIndex = true
	}

	if c.Fallback != "" {
		u, err := url.Parse(c.Fallback)
		if err != nil {
			return nil, err
		}
		if !strings.HasPrefix(u.Scheme, "http") {
			return nil, fmt.Errorf("Invalid fallback protocol scheme")
		}
		s.fallbackHost = u.Host
		s.fallback = httputil.NewSingleHostReverseProxy(u)
	}

	if c.LiveReload {
		s.lr, _ = lrserver.New("serve-lr", lrserver.DefaultPort)
		discard := log.New(ioutil.Discard, "", 0)
		s.lr.SetErrorLog(discard)
		s.lr.SetStatusLog(discard)
		s.watching = map[string]bool{}
		s.watcher, err = fsnotify.NewWatcher()
		if err != nil {
			return nil, err
		}
	}

	return s, nil
}

func (s *Server) Start() error {

	if s.c.Open {
		go func() {
			time.Sleep(500 * time.Millisecond)
			cmd := exec.Command("open", "http://localhost:"+s.port)
			cmd.Run()
		}()
	}

	if s.c.LiveReload {
		go func() {
			if err := s.lr.ListenAndServe(); err != nil {
				fmt.Printf("LiveReload server closed: %s", err)
			}
		}()
		go func() {
			for {
				event := <-s.watcher.Events
				switch event.Op {
				case fsnotify.Create, fsnotify.Rename:
					s.lr.Reload(event.Name)
				}
			}
		}()
	}

	h := http.Handler(http.HandlerFunc(s.serve))

	//logging is enabled
	if !s.c.Quiet {
		introTemplate.Execute(os.Stdout, &struct {
			*colors
			Dir, Port string
		}{
			s.colors,
			s.c.Directory, s.port,
		})
	}
	//listen
	return http.ListenAndServe(s.addr, h)
}

func (s *Server) serve(w http.ResponseWriter, r *http.Request) {

	//when logs are enabled, swap out response writer with
	//inspectable version
	if !s.c.Quiet {
		sw := &ServeWriter{w: w}
		w = sw
		//track timing
		t0 := time.Now()
		defer func() {
			t := time.Now().Sub(t0)
			//show ip if external
			ip := ""
			h, _, _ := net.SplitHostPort(r.RemoteAddr)
			if h != "127.0.0.1" && h != "::1" {
				ip = h
			}
			cc := ""
			if !s.c.NoColor {
				cc = colorcode(sw.Code)
			}
			logTemplate.Execute(os.Stdout, &struct {
				*colors
				Timestamp, Method, Path, CodeColor string
				Code                               int
				Duration, Size, IP                 string
			}{
				s.colors,
				t0.Format(s.c.TimeFmt), r.Method, r.URL.Path, cc,
				sw.Code,
				fmtduration(t), sizestr.ToString(sw.Size), ip,
			})
		}()
	}

	path := r.URL.Path
	//shorthand
	reply := func(c int, msg string) {
		w.WriteHeader(c)
		if msg != "" {
			w.Write([]byte(msg))
		}
	}
	//requested file
	p := filepath.Join(s.c.Directory, path)
	//check file or dir
	isdir := false
	missing := false
	if info, err := os.Stat(p); err != nil {
		missing = true
	} else {
		isdir = info.IsDir()
	}

	if s.c.PushState && missing && filepath.Ext(p) == "" {
		//missing and pushstate and no ext
		p = s.root //change to request for the root
		isdir = false
		missing = false
	}

	if s.fallback != nil && (missing || isdir) {
		//fallback proxy enabled
		r.Host = s.fallbackHost
		s.fallback.ServeHTTP(w, r)
		return
	}

	if !s.c.NoArchive && missing {
		//check if is archivable
		ok := false
		ext := archive.Extension(p)
		dir := ""
		if ext != "" {
			var err error
			if dir, err = filepath.Abs(strings.TrimSuffix(p, ext)); err == nil {
				if info, err := os.Stat(dir); err == nil && info.IsDir() {
					ok = true
				}
			}
		}
		if ok {
			w.Header().Set("Content-Type", mime.TypeByExtension(ext))
			w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(dir)+ext)
			w.WriteHeader(200)
			//write archive
			a, _ := archive.NewWriter(ext, w)
			if err := a.AddDir(dir); err != nil {
				w.Write([]byte("\n\nERROR: " + err.Error()))
				return
			}
			if err := a.Close(); err != nil {
				w.Write([]byte("\n\nERROR: " + err.Error()))
				return
			}
			return
		}
	}

	if !isdir && missing {
		//file not found!!
		reply(404, "Not found")
		return
	}

	//force trailing slash
	if isdir && !s.c.NoSlash && !strings.HasSuffix(path, "/") {
		w.Header().Set("Location", path+"/")
		w.WriteHeader(302)
		w.Write([]byte("Redirecting (must use slash for directories)"))
		return
	}

	//optionally use index instead of directory list
	if isdir && !s.c.NoIndex {
		dirindex := filepath.Join(p, "index.html")
		if _, err := os.Stat(dirindex); err == nil {
			p = dirindex
			isdir = false
		}
	}

	//directory list
	if isdir {
		if s.c.NoList {
			reply(403, "Listing not allowed")
			return
		}
		s.dirlist(w, r, p)
		return
	}

	//check file again
	info, err := os.Stat(p)
	if err != nil {
		reply(404, "Not found")
		return
	}

	//stream file
	f, err := os.Open(p)
	if err != nil {
		reply(500, err.Error())
		return
	}

	//add all served file's parent dirs to the watcher
	if s.c.LiveReload {
		dir, _ := filepath.Split(p)
		if _, watching := s.watching[dir]; !watching {
			if err := s.watcher.Add(dir); err == nil {
				s.watching[dir] = true
			}
		}
	}

	modtime := info.ModTime()
	//first time - dont use cache
	if !s.served[p] {
		s.served[p] = true
		modtime = time.Now()
	}

	//http.ServeContent handles caching and range requests
	http.ServeContent(w, r, info.Name(), modtime, f)
}
<|endoftext|>


<commit_before>// Package server implements a configurable, general-purpose web server.
// It relies on configurations obtained from the adjacent config package
// and can execute middleware as defined by the adjacent middleware package.
package server

import (
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"

	"github.com/bradfitz/http2"
)

// Server represents an instance of a server, which serves
// static content at a particular address (host and port).
type Server struct {
	HTTP2   bool                   // temporary while http2 is not in std lib (TODO: remove flag when part of std lib)
	address string                 // the actual address for net.Listen to listen on
	tls     bool                   // whether this server is serving all HTTPS hosts or not
	vhosts  map[string]virtualHost // virtual hosts keyed by their address
}

// New creates a new Server which will bind to addr and serve
// the sites/hosts configured in configs. This function does
// not start serving.
func New(addr string, configs []Config, tls bool) (*Server, error) {
	s := &Server{
		address: addr,
		tls:     tls,
		vhosts:  make(map[string]virtualHost),
	}

	for _, conf := range configs {
		if _, exists := s.vhosts[conf.Host]; exists {
			return nil, fmt.Errorf("Cannot serve %s - host already defined for address %s", conf.Address(), s.address)
		}

		vh := virtualHost{config: conf}

		// Build middleware stack
		err := vh.buildStack()
		if err != nil {
			return nil, err
		}

		s.vhosts[conf.Host] = vh
	}

	return s, nil
}

// Serve starts the server. It blocks until the server quits.
func (s *Server) Serve() error {
	server := &http.Server{
		Addr:    s.address,
		Handler: s,
	}

	if s.HTTP2 {
		// TODO: This call may not be necessary after HTTP/2 is merged into std lib
		http2.ConfigureServer(server, nil)
	}

	for _, vh := range s.vhosts {
		// Execute startup functions now
		for _, start := range vh.config.Startup {
			err := start()
			if err != nil {
				return err
			}
		}

		// Execute shutdown commands on exit
		if len(vh.config.Shutdown) > 0 {
			go func() {
				interrupt := make(chan os.Signal, 1)
				signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
				<-interrupt
				for _, shutdownFunc := range vh.config.Shutdown {
					err := shutdownFunc()
					if err != nil {
						log.Fatal(err)
					}
				}
				os.Exit(0)
			}()
		}
	}

	if s.tls {
		var tlsConfigs []TLSConfig
		for _, vh := range s.vhosts {
			tlsConfigs = append(tlsConfigs, vh.config.TLS)
		}
		return ListenAndServeTLSWithSNI(server, tlsConfigs)
	} else {
		return server.ListenAndServe()
	}
}

// ListenAndServeTLSWithSNI serves TLS with Server Name Indication (SNI) support, which allows
// multiple sites (different hostnames) to be served from the same address. This method is
// adapted directly from the std lib's net/http ListenAndServeTLS function, which was
// written by the Go Authors. It has been modified to support multiple certificate/key pairs.
func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error {
	addr := srv.Addr
	if addr == "" {
		addr = ":https"
	}

	config := new(tls.Config)
	if srv.TLSConfig != nil {
		*config = *srv.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	// Here we diverge from the stdlib a bit by loading multiple certs/key pairs
	// then we map the server names to their certs
	var err error
	config.Certificates = make([]tls.Certificate, len(tlsConfigs))
	for i, tlsConfig := range tlsConfigs {
		config.Certificates[i], err = tls.LoadX509KeyPair(tlsConfig.Certificate, tlsConfig.Key)
		if err != nil {
			return err
		}
	}
	config.BuildNameToCertificate()

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	tlsListener := tls.NewListener(conn, config)
	return srv.Serve(tlsListener)
}

// ServeHTTP is the entry point for every request to the address that s
// is bound to. It acts as a multiplexer for the requests hostname as
// defined in the Host header so that the correct virtualhost
// (configuration and middleware stack) will handle the request.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer func() {
		// In case the user doesn't enable error middleware, we still
		// need to make sure that we stay alive up here
		if rec := recover(); rec != nil {
			http.Error(w, http.StatusText(http.StatusInternalServerError),
				http.StatusInternalServerError)
		}
	}()

	host, _, err := net.SplitHostPort(r.Host)
	if err != nil {
		host = r.Host // oh well
	}

	// Try the host as given, or try falling back to 0.0.0.0 (wildcard)
	if _, ok := s.vhosts[host]; !ok {
		if _, ok2 := s.vhosts["0.0.0.0"]; ok2 {
			host = "0.0.0.0"
		}
	}

	if vh, ok := s.vhosts[host]; ok {
		w.Header().Set("Server", "Caddy")

		status, _ := vh.stack.ServeHTTP(w, r)

		// Fallback error response in case error handling wasn't chained in
		if status >= 400 {
			w.WriteHeader(status)
			fmt.Fprintf(w, "%d %s", status, http.StatusText(status))
		}
	} else {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "No such host at %s", s.address)
	}
}
<commit_msg>adding crypto/tls sessioncache<commit_after>// Package server implements a configurable, general-purpose web server.
// It relies on configurations obtained from the adjacent config package
// and can execute middleware as defined by the adjacent middleware package.
package server

import (
	"crypto/tls"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"

	"github.com/bradfitz/http2"
)

// Server represents an instance of a server, which serves
// static content at a particular address (host and port).
type Server struct {
	HTTP2   bool                   // temporary while http2 is not in std lib (TODO: remove flag when part of std lib)
	address string                 // the actual address for net.Listen to listen on
	tls     bool                   // whether this server is serving all HTTPS hosts or not
	vhosts  map[string]virtualHost // virtual hosts keyed by their address
}

// New creates a new Server which will bind to addr and serve
// the sites/hosts configured in configs. This function does
// not start serving.
func New(addr string, configs []Config, tls bool) (*Server, error) {
	s := &Server{
		address: addr,
		tls:     tls,
		vhosts:  make(map[string]virtualHost),
	}

	for _, conf := range configs {
		if _, exists := s.vhosts[conf.Host]; exists {
			return nil, fmt.Errorf("Cannot serve %s - host already defined for address %s", conf.Address(), s.address)
		}

		vh := virtualHost{config: conf}

		// Build middleware stack
		err := vh.buildStack()
		if err != nil {
			return nil, err
		}

		s.vhosts[conf.Host] = vh
	}

	return s, nil
}

// Serve starts the server. It blocks until the server quits.
func (s *Server) Serve() error {
	server := &http.Server{
		Addr:    s.address,
		Handler: s,
	}

	if s.HTTP2 {
		// TODO: This call may not be necessary after HTTP/2 is merged into std lib
		http2.ConfigureServer(server, nil)
	}

	for _, vh := range s.vhosts {
		// Execute startup functions now
		for _, start := range vh.config.Startup {
			err := start()
			if err != nil {
				return err
			}
		}

		// Execute shutdown commands on exit
		if len(vh.config.Shutdown) > 0 {
			go func() {
				interrupt := make(chan os.Signal, 1)
				signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
				<-interrupt
				for _, shutdownFunc := range vh.config.Shutdown {
					err := shutdownFunc()
					if err != nil {
						log.Fatal(err)
					}
				}
				os.Exit(0)
			}()
		}
	}

	if s.tls {
		var tlsConfigs []TLSConfig
		for _, vh := range s.vhosts {
			tlsConfigs = append(tlsConfigs, vh.config.TLS)
		}
		return ListenAndServeTLSWithSNI(server, tlsConfigs)
	} else {
		return server.ListenAndServe()
	}
}

// ListenAndServeTLSWithSNI serves TLS with Server Name Indication (SNI) support, which allows
// multiple sites (different hostnames) to be served from the same address. This method is
// adapted directly from the std lib's net/http ListenAndServeTLS function, which was
// written by the Go Authors. It has been modified to support multiple certificate/key pairs.
func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error {
	addr := srv.Addr
	if addr == "" {
		addr = ":https"
	}

	config := new(tls.Config)
	if srv.TLSConfig != nil {
		*config = *srv.TLSConfig
	}
	if config.NextProtos == nil {
		config.NextProtos = []string{"http/1.1"}
	}

	// Here we diverge from the stdlib a bit by loading multiple certs/key pairs
	// then we map the server names to their certs
	var err error
	config.Certificates = make([]tls.Certificate, len(tlsConfigs))
	for i, tlsConfig := range tlsConfigs {
		config.Certificates[i], err = tls.LoadX509KeyPair(tlsConfig.Certificate, tlsConfig.Key)
		if err != nil {
			return err
		}
	}
	config.BuildNameToCertificate()

	// Add a session cache LRU algorithm with default capacity (64)
	config.ClientSessionCache = tls.NewLRUClientSessionCache(0)

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	tlsListener := tls.NewListener(conn, config)
	return srv.Serve(tlsListener)
}

// ServeHTTP is the entry point for every request to the address that s
// is bound to. It acts as a multiplexer for the requests hostname as
// defined in the Host header so that the correct virtualhost
// (configuration and middleware stack) will handle the request.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer func() {
		// In case the user doesn't enable error middleware, we still
		// need to make sure that we stay alive up here
		if rec := recover(); rec != nil {
			http.Error(w, http.StatusText(http.StatusInternalServerError),
				http.StatusInternalServerError)
		}
	}()

	host, _, err := net.SplitHostPort(r.Host)
	if err != nil {
		host = r.Host // oh well
	}

	// Try the host as given, or try falling back to 0.0.0.0 (wildcard)
	if _, ok := s.vhosts[host]; !ok {
		if _, ok2 := s.vhosts["0.0.0.0"]; ok2 {
			host = "0.0.0.0"
		}
	}

	if vh, ok := s.vhosts[host]; ok {
		w.Header().Set("Server", "Caddy")

		status, _ := vh.stack.ServeHTTP(w, r)

		// Fallback error response in case error handling wasn't chained in
		if status >= 400 {
			w.WriteHeader(status)
			fmt.Fprintf(w, "%d %s", status, http.StatusText(status))
		}
	} else {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "No such host at %s", s.address)
	}
}
<|endoftext|>


<commit_before>// Copyright 2016, RadiantBlue Technologies, 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.

package server

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/venicegeo/pz-gocommon"
	"github.com/venicegeo/pz-gocommon/elasticsearch"
	"github.com/venicegeo/pz-logger/client"
)

type LockedAdminSettings struct {
	sync.Mutex
	client.LoggerAdminSettings
}

var settings LockedAdminSettings

type LockedAdminStats struct {
	sync.Mutex
	client.LoggerAdminStats
}

var stats LockedAdminStats

type LogData struct {
	sync.Mutex
	esIndex elasticsearch.IIndex
	id      int
}

var logData LogData

var schema = "LogData"

func initServer(sys *piazza.SystemConfig, esIndex elasticsearch.IIndex) {
	var err error

	stats.StartTime = time.Now()

	if !esIndex.IndexExists() {
		err = esIndex.Create()
		if err != nil {
			log.Fatal(err)
		}
		mapping :=
			`{
		    "LogData":{
			    "properties":{
				    "service":{
					    "type": "string",
                        "store": true
    			    },
				    "address":{
					    "type": "string",
                        "store": true
    			    },
				    "time":{
					    "type": "string",
                        "store": true
    			    },
				    "severity":{
					    "type": "string",
                        "store": true
    			    },
				    "message":{
					    "type": "string",
                        "store": true
    			    }
	    	    }
	        }
        }`

		err = esIndex.SetMapping(schema, piazza.JsonString(mapping))
		if err != nil {
			log.Fatal(err)
		}
	}

	logData.esIndex = esIndex
}

func handleGetRoot(c *gin.Context) {
	//log.Print("got health-check request")
	c.String(http.StatusOK, "Hi. I'm pz-logger.")
}

func handlePostMessages(c *gin.Context) {
	var mssg client.LogMessage
	err := c.BindJSON(&mssg)
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}

	err = mssg.Validate()
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}

	log.Printf("PZLOG: %s\n", mssg.String())

	logData.Lock()
	idStr := strconv.Itoa(logData.id)
	logData.id++
	logData.Unlock()
	indexResult, err := logData.esIndex.PostData(schema, idStr, mssg)
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}
	if !indexResult.Created {
		c.String(http.StatusBadRequest, "POST of log data failed")
		return
	}

	err = logData.esIndex.Flush()
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}

	stats.LoggerAdminStats.NumMessages++

	c.JSON(http.StatusOK, nil)
}

func handleGetAdminStats(c *gin.Context) {
	logData.Lock()
	t := stats.LoggerAdminStats
	logData.Unlock()
	c.JSON(http.StatusOK, t)
}

func handleGetAdminSettings(c *gin.Context) {
	settings.Lock()
	t := settings.LoggerAdminSettings
	settings.Unlock()
	c.JSON(http.StatusOK, t)
}

func handlePostAdminSettings(c *gin.Context) {
	t := client.LoggerAdminSettings{}
	err := c.BindJSON(&t)
	if err != nil {
		c.Error(err)
		return
	}
	settings.Lock()
	settings.LoggerAdminSettings = t
	settings.Unlock()
	c.String(http.StatusOK, "")
}

func handlePostAdminShutdown(c *gin.Context) {
	piazza.HandlePostAdminShutdown(c)
}

func handleGetMessages(c *gin.Context) {
	var err error
	count := 128
	key := c.Query("count")
	if key != "" {
		count, err = strconv.Atoi(key)
		if err != nil {
			c.String(http.StatusBadRequest, "query argument invalid: %s", key)
			return
		}
	}

	// copy up to count elements from the end of the log array

	searchResult, err := logData.esIndex.FilterByMatchAll(schema)
	if err != nil {
		c.String(http.StatusBadRequest, "query failed: %s", err)
		return
	}

	// TODO: unsafe truncation
	l := int(searchResult.TotalHits())
	if count > l {
		count = l
	}
	lines := make([]client.LogMessage, count)

	i := 0
	for _, hit := range *searchResult.GetHits() {
		tmp := &client.LogMessage{}
		src := *hit.Source
		log.Printf("source hit: %s", string(src))
		err = json.Unmarshal(src, tmp)
		if err != nil {
			log.Printf("UNABLE TO PARSE: %s", string(*hit.Source))
			c.String(http.StatusBadRequest, "query unmarshal failed: %s", err)
			return
		}
		lines[i] = *tmp
		i++
	}

	c.JSON(http.StatusOK, lines)
}

func CreateHandlers(sys *piazza.SystemConfig, esi elasticsearch.IIndex) http.Handler {
	initServer(sys, esi)

	gin.SetMode(gin.ReleaseMode)
	router := gin.New()
	//router.Use(gin.Logger())
	//router.Use(gin.Recovery())

	router.GET("/", func(c *gin.Context) { handleGetRoot(c) })

	router.POST("/v1/messages", func(c *gin.Context) { handlePostMessages(c) })
	router.GET("/v1/messages", func(c *gin.Context) { handleGetMessages(c) })

	router.GET("/v1/admin/stats", func(c *gin.Context) { handleGetAdminStats(c) })

	router.GET("/v1/admin/settings", func(c *gin.Context) { handleGetAdminSettings(c) })
	router.POST("/v1/admin/settings", func(c *gin.Context) { handlePostAdminSettings(c) })

	router.POST("/v1/admin/shutdown", func(c *gin.Context) { handlePostAdminShutdown(c) })

	return router
}
<commit_msg>add test for nil search result<commit_after>// Copyright 2016, RadiantBlue Technologies, 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.

package server

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/venicegeo/pz-gocommon"
	"github.com/venicegeo/pz-gocommon/elasticsearch"
	"github.com/venicegeo/pz-logger/client"
)

type LockedAdminSettings struct {
	sync.Mutex
	client.LoggerAdminSettings
}

var settings LockedAdminSettings

type LockedAdminStats struct {
	sync.Mutex
	client.LoggerAdminStats
}

var stats LockedAdminStats

type LogData struct {
	sync.Mutex
	esIndex elasticsearch.IIndex
	id      int
}

var logData LogData

var schema = "LogData"

func initServer(sys *piazza.SystemConfig, esIndex elasticsearch.IIndex) {
	var err error

	stats.StartTime = time.Now()

	if !esIndex.IndexExists() {
		err = esIndex.Create()
		if err != nil {
			log.Fatal(err)
		}
		mapping :=
			`{
		    "LogData":{
			    "properties":{
				    "service":{
					    "type": "string",
                        "store": true
    			    },
				    "address":{
					    "type": "string",
                        "store": true
    			    },
				    "time":{
					    "type": "string",
                        "store": true
    			    },
				    "severity":{
					    "type": "string",
                        "store": true
    			    },
				    "message":{
					    "type": "string",
                        "store": true
    			    }
	    	    }
	        }
        }`

		err = esIndex.SetMapping(schema, piazza.JsonString(mapping))
		if err != nil {
			log.Fatal(err)
		}
	}

	logData.esIndex = esIndex
}

func handleGetRoot(c *gin.Context) {
	//log.Print("got health-check request")
	c.String(http.StatusOK, "Hi. I'm pz-logger.")
}

func handlePostMessages(c *gin.Context) {
	var mssg client.LogMessage
	err := c.BindJSON(&mssg)
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}

	err = mssg.Validate()
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}

	log.Printf("PZLOG: %s\n", mssg.String())

	logData.Lock()
	idStr := strconv.Itoa(logData.id)
	logData.id++
	logData.Unlock()
	indexResult, err := logData.esIndex.PostData(schema, idStr, mssg)
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}
	if !indexResult.Created {
		c.String(http.StatusBadRequest, "POST of log data failed")
		return
	}

	err = logData.esIndex.Flush()
	if err != nil {
		c.String(http.StatusBadRequest, "%v", err)
		return
	}

	stats.LoggerAdminStats.NumMessages++

	c.JSON(http.StatusOK, nil)
}

func handleGetAdminStats(c *gin.Context) {
	logData.Lock()
	t := stats.LoggerAdminStats
	logData.Unlock()
	c.JSON(http.StatusOK, t)
}

func handleGetAdminSettings(c *gin.Context) {
	settings.Lock()
	t := settings.LoggerAdminSettings
	settings.Unlock()
	c.JSON(http.StatusOK, t)
}

func handlePostAdminSettings(c *gin.Context) {
	t := client.LoggerAdminSettings{}
	err := c.BindJSON(&t)
	if err != nil {
		c.Error(err)
		return
	}
	settings.Lock()
	settings.LoggerAdminSettings = t
	settings.Unlock()
	c.String(http.StatusOK, "")
}

func handlePostAdminShutdown(c *gin.Context) {
	piazza.HandlePostAdminShutdown(c)
}

func handleGetMessages(c *gin.Context) {
	var err error
	count := 128
	key := c.Query("count")
	if key != "" {
		count, err = strconv.Atoi(key)
		if err != nil {
			c.String(http.StatusBadRequest, "query argument invalid: %s", key)
			return
		}
	}

	// copy up to count elements from the end of the log array

	searchResult, err := logData.esIndex.FilterByMatchAll(schema)
	if err != nil {
		c.String(http.StatusBadRequest, "query failed: %s", err)
		return
	}

	// TODO: unsafe truncation
	l := int(searchResult.TotalHits())
	if count > l {
		count = l
	}
	lines := make([]client.LogMessage, count)

	i := 0
	for _, hit := range *searchResult.GetHits() {
		if hit == nil {
			log.Printf("null source hit")
			continue
		}
		src := *hit.Source
		log.Printf("source hit: %s", string(src))

		tmp := &client.LogMessage{}
		err = json.Unmarshal(src, tmp)
		if err != nil {
			log.Printf("UNABLE TO PARSE: %s", string(*hit.Source))
			c.String(http.StatusBadRequest, "query unmarshal failed: %s", err)
			return
		}
		lines[i] = *tmp
		i++
	}

	c.JSON(http.StatusOK, lines)
}

func CreateHandlers(sys *piazza.SystemConfig, esi elasticsearch.IIndex) http.Handler {
	initServer(sys, esi)

	gin.SetMode(gin.ReleaseMode)
	router := gin.New()
	//router.Use(gin.Logger())
	//router.Use(gin.Recovery())

	router.GET("/", func(c *gin.Context) { handleGetRoot(c) })

	router.POST("/v1/messages", func(c *gin.Context) { handlePostMessages(c) })
	router.GET("/v1/messages", func(c *gin.Context) { handleGetMessages(c) })

	router.GET("/v1/admin/stats", func(c *gin.Context) { handleGetAdminStats(c) })

	router.GET("/v1/admin/settings", func(c *gin.Context) { handleGetAdminSettings(c) })
	router.POST("/v1/admin/settings", func(c *gin.Context) { handlePostAdminSettings(c) })

	router.POST("/v1/admin/shutdown", func(c *gin.Context) { handlePostAdminShutdown(c) })

	return router
}
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Robin Engel
 *
 * 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 server

import (
	"github.com/gorilla/mux"
	"net/http"
	"log"
	"fmt"
	"strconv"
	"github.com/bluedevel/mosel/server/handler"
	"github.com/bluedevel/mosel/server/core"
)

type moselServer struct {
	config  MoselServerConfig
	context core.MoselServerContext
}

func NewMoselServer(config MoselServerConfig) *moselServer {
	server := new(moselServer)
	server.config = config

	return server
}

func (server *moselServer) Run() error {

	err := server.initContext()

	if ! server.context.IsInitialized {

		if err != nil {
			return err
		}

		return fmt.Errorf("Mosel Server - Run: Context wasn't initialized correctly")
	}

	r := mux.NewRouter()
	server.initHandler(r)
	http.Handle("/", r)

	addr := server.config.Http.BindAddress
	log.Printf("Binding http server to %s", addr)
	return http.ListenAndServe(addr, nil)
}

func (server *moselServer) initContext() error {

	initFns := []func() error{
		server.initAuth,
		server.initSessionCache,
	}

	for _, fn := range initFns {
		err := fn()

		if (err != nil) {
			return err
		}
	}

	server.context.IsInitialized = true
	return nil
}

func (server *moselServer) initAuth() error {
	config := server.config

	var enabledCount int = 0

	if config.AuthSys.Enabled {
		enabledCount++
	}

	if config.AuthMySQL.Enabled {
		enabledCount++
	}

	if config.AuthTrue.Enabled {
		enabledCount++
		log.Println("Using AuthTrue! This is for debug purposes only, make sure you don't deploy this in production")
		server.context.Auth = core.AuthTrue{}
	}

	if enabledCount > 1 {
		return fmt.Errorf("More then one auth services enabled")
	} else if enabledCount == 0 {
		return fmt.Errorf("No auth service configured")
	}

	return nil
}

func (server *moselServer) initSessionCache() error {
	c := core.NewSessionCache()
	server.context.Sessions = *c
	return nil
}

func (server *moselServer) initHandler(r *mux.Router) {

	var handlers = []MoselHandler{
		handler.NewPingHandler(),
		handler.NewLoginHandler(),
	}

	for n, _ := range handlers {

		h := handlers[n]

		f := func(w http.ResponseWriter, r *http.Request) {
			h.ServeHTTPContext(server.context, w, r)
		}

		secure := h.Secure()

		if secure {
			f = server.secure(server.context, f)
		}

		log.Printf("Handling %s - secure=%s", h.GetPath(), strconv.FormatBool(secure))
		r.HandleFunc(h.GetPath(), f)
	}
}<commit_msg>add errors channel<commit_after>/*
 * Copyright 2016 Robin Engel
 *
 * 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 server

import (
	"github.com/gorilla/mux"
	"net/http"
	"log"
	"fmt"
	"strconv"
	"github.com/bluedevel/mosel/server/handler"
	"github.com/bluedevel/mosel/server/core"
)

type moselServer struct {
	config  MoselServerConfig
	context core.MoselServerContext
}

func NewMoselServer(config MoselServerConfig) *moselServer {
	server := new(moselServer)
	server.config = config

	return server
}

func (server *moselServer) Run() error {

	err := server.initContext()

	if ! server.context.IsInitialized {

		if err != nil {
			return err
		}

		return fmt.Errorf("Mosel Server - Run: Context wasn't initialized correctly")
	}

	r := mux.NewRouter()
	server.initHandler(r)
	http.Handle("/", r)

	addr := server.config.Http.BindAddress
	log.Printf("Binding http server to %s", addr)

	errors := make(chan error)
	go func() {
		errors <- http.ListenAndServe(addr, nil)
	}()

	return <-errors
}

func (server *moselServer) initContext() error {

	initFns := []func() error{
		server.initAuth,
		server.initSessionCache,
	}

	for _, fn := range initFns {
		err := fn()

		if (err != nil) {
			return err
		}
	}

	server.context.IsInitialized = true
	return nil
}

func (server *moselServer) initAuth() error {
	config := server.config

	var enabledCount int = 0

	if config.AuthSys.Enabled {
		enabledCount++
	}

	if config.AuthMySQL.Enabled {
		enabledCount++
	}

	if config.AuthTrue.Enabled {
		enabledCount++
		log.Println("Using AuthTrue! This is for debug purposes only, make sure you don't deploy this in production")
		server.context.Auth = core.AuthTrue{}
	}

	if enabledCount > 1 {
		return fmt.Errorf("More then one auth services enabled")
	} else if enabledCount == 0 {
		return fmt.Errorf("No auth service configured")
	}

	return nil
}

func (server *moselServer) initSessionCache() error {
	c := core.NewSessionCache()
	server.context.Sessions = *c
	return nil
}

func (server *moselServer) initHandler(r *mux.Router) {

	var handlers = []MoselHandler{
		handler.NewPingHandler(),
		handler.NewLoginHandler(),
	}

	for n, _ := range handlers {

		h := handlers[n]

		f := func(w http.ResponseWriter, r *http.Request) {
			h.ServeHTTPContext(server.context, w, r)
		}

		secure := h.Secure()

		if secure {
			f = server.secure(server.context, f)
		}

		log.Printf("Handling %s - secure=%s", h.GetPath(), strconv.FormatBool(secure))
		r.HandleFunc(h.GetPath(), f)
	}
}<|endoftext|>


<commit_before>// confusion idea from: https://crawl.develz.org/tavern/viewtopic.php?f=17&t=24108&sid=cb465fe78aba3b9074a32efc2a835d80#p318813

package main

type status int

const (
	StatusBerserk status = iota
	StatusSlow
	StatusExhausted
	StatusSwift
	StatusAgile
	StatusLignification
	StatusConfusion
	StatusTele
	StatusNausea
	StatusDisabledShield
	StatusCorrosion
	StatusFlames // fake status
)

func (st status) Good() bool {
	switch st {
	case StatusBerserk, StatusSwift, StatusAgile:
		return true
	default:
		return false
	}
}

func (st status) Bad() bool {
	switch st {
	case StatusSlow, StatusConfusion, StatusNausea, StatusDisabledShield, StatusFlames:
		return true
	default:
		return false
	}
}

func (st status) String() string {
	switch st {
	case StatusBerserk:
		return "Berserk"
	case StatusSlow:
		return "Slow"
	case StatusExhausted:
		return "Exhausted"
	case StatusSwift:
		return "Swift"
	case StatusLignification:
		return "Lignified"
	case StatusAgile:
		return "Agile"
	case StatusConfusion:
		return "Confused"
	case StatusTele:
		return "Tele"
	case StatusNausea:
		return "Nausea"
	case StatusDisabledShield:
		return "-Shield"
	case StatusCorrosion:
		return "Corroded"
	case StatusFlames:
		return "Flames"
	default:
		// should not happen
		return "unknown"
	}
}
<commit_msg>corrosion status is bad<commit_after>// confusion idea from: https://crawl.develz.org/tavern/viewtopic.php?f=17&t=24108&sid=cb465fe78aba3b9074a32efc2a835d80#p318813

package main

type status int

const (
	StatusBerserk status = iota
	StatusSlow
	StatusExhausted
	StatusSwift
	StatusAgile
	StatusLignification
	StatusConfusion
	StatusTele
	StatusNausea
	StatusDisabledShield
	StatusCorrosion
	StatusFlames // fake status
)

func (st status) Good() bool {
	switch st {
	case StatusBerserk, StatusSwift, StatusAgile:
		return true
	default:
		return false
	}
}

func (st status) Bad() bool {
	switch st {
	case StatusSlow, StatusConfusion, StatusNausea, StatusDisabledShield, StatusFlames, StatusCorrosion:
		return true
	default:
		return false
	}
}

func (st status) String() string {
	switch st {
	case StatusBerserk:
		return "Berserk"
	case StatusSlow:
		return "Slow"
	case StatusExhausted:
		return "Exhausted"
	case StatusSwift:
		return "Swift"
	case StatusLignification:
		return "Lignified"
	case StatusAgile:
		return "Agile"
	case StatusConfusion:
		return "Confused"
	case StatusTele:
		return "Tele"
	case StatusNausea:
		return "Nausea"
	case StatusDisabledShield:
		return "-Shield"
	case StatusCorrosion:
		return "Corroded"
	case StatusFlames:
		return "Flames"
	default:
		// should not happen
		return "unknown"
	}
}
<|endoftext|>


<commit_before>package spdy

import (
  "bytes"
  "fmt"
  "log"
  "net/http"
  "sync"
)

type stream struct {
  sync.RWMutex
  conn           *connection
  streamID       uint32
  flow           *flowControl
  requestBody    *bytes.Buffer
  state          StreamState
  input          <-chan Frame
  output         chan<- Frame
  request        *Request
  handler        *ServeMux
  certificates   []Certificate
  headers        Header
  settings       []*Setting
  unidirectional bool
  responseSent   bool
  responseCode   int
  stop           bool
  wroteHeader    bool
  version        int
}

func (s *stream) Header() Header {
  return s.headers
}

func (s *stream) Ping() <-chan bool {
  return s.conn.Ping()
}

func (s *stream) Push() (PushWriter, error) {
  return nil, nil
}

func (s *stream) Settings() []*Setting {
  return s.conn.receivedSettings
}

func (s *stream) Write(inputData []byte) (int, error) {
  s.processInput()
  if s.stop {
    return 0, ErrCancelled
  }

  // Dereference the pointer.
  data := make([]byte, len(inputData))
  copy(data, inputData)

  if !s.wroteHeader {
    s.WriteHeader(http.StatusOK)
  }

  written := 0
  for len(data) > MAX_DATA_SIZE {
    n, err := s.flow.Write(data[:MAX_DATA_SIZE])
    if err != nil {
      return written, err
    }
    written += n
    data = data[MAX_DATA_SIZE:]
  }

  n, err := s.flow.Write(data)
  written += n

  return written, err
}

func (s *stream) WriteHeader(code int) {
  if s.wroteHeader {
    log.Println("spdy: Error: Multiple calls to ResponseWriter.WriteHeader.")
    return
  }

  s.wroteHeader = true
  s.responseCode = code

  s.headers.Set(":status", fmt.Sprint(code))
  s.headers.Set(":version", "HTTP/1.1")

  synReply := new(SynReplyFrame)
  synReply.version = uint16(s.version)
  synReply.StreamID = s.streamID
  synReply.Headers = s.headers

  s.output <- synReply
}

func (s *stream) WriteSettings(settings ...*Setting) {
  if settings == nil {
    return
  }

  frame := new(SettingsFrame)
  frame.version = uint16(s.version)
  frame.Settings = settings
  s.output <- frame
}

func (s *stream) receiveFrame(frame Frame) {
  if frame == nil {
    panic("Nil frame received in receiveFrame.")
  }

  switch frame := frame.(type) {
  case *DataFrame:
    s.requestBody.Write(frame.Data)

  case *HeadersFrame:
    s.headers.Update(frame.Headers)

  case *WindowUpdateFrame:
    err := s.flow.UpdateWindow(frame.DeltaWindowSize)
    if err != nil {
      reply := new(RstStreamFrame)
      reply.version = uint16(s.version)
      reply.StreamID = s.streamID
      reply.StatusCode = RST_STREAM_FLOW_CONTROL_ERROR
      s.output <- reply
      return
    }

  default:
    panic(fmt.Sprintf("Received unknown frame of type %T.", frame))
  }
}

func (s *stream) wait() {
  frame := <-s.input
  if frame == nil {
    return
  }
  s.receiveFrame(frame)
}

func (s *stream) processInput() {
  var frame Frame
  var ok bool

  for {
    select {
    case frame, ok = <-s.input:
      if !ok {
        return
      }
      s.receiveFrame(frame)

    default:
      return
    }
  }
}

func (s *stream) run() {

  // Make sure Request is prepared.
  s.AddFlowControl()
  s.requestBody = new(bytes.Buffer)
  s.processInput()
  s.request.Body = &readCloserBuffer{s.requestBody}

  /***************
   *** HANDLER ***
   ***************/
  s.handler.ServeSPDY(s, s.request)

  // Make sure any queued data has been sent.
  for s.flow.Paused() {
    s.wait()
    s.flow.Flush()
  }

  if !s.wroteHeader {
    s.headers.Set(":status", "200")
    s.headers.Set(":version", "HTTP/1.1")

    synReply := new(SynReplyFrame)
    synReply.version = uint16(s.version)
    synReply.Flags = FLAG_FIN
    synReply.StreamID = s.streamID
    synReply.Headers = s.headers

    s.output <- synReply
  } else {
    data := new(DataFrame)
    data.StreamID = s.streamID
    data.Flags = FLAG_FIN
    data.Data = []byte{}

    s.output <- data
  }

  s.conn.done.Done()
}

type readCloserBuffer struct {
  *bytes.Buffer
}

func (_ *readCloserBuffer) Close() error {
  return nil
}
<commit_msg>Ensured state updated when stream ends.<commit_after>package spdy

import (
  "bytes"
  "fmt"
  "log"
  "net/http"
  "sync"
)

type stream struct {
  sync.RWMutex
  conn           *connection
  streamID       uint32
  flow           *flowControl
  requestBody    *bytes.Buffer
  state          StreamState
  input          <-chan Frame
  output         chan<- Frame
  request        *Request
  handler        *ServeMux
  certificates   []Certificate
  headers        Header
  settings       []*Setting
  unidirectional bool
  responseSent   bool
  responseCode   int
  stop           bool
  wroteHeader    bool
  version        int
}

func (s *stream) Header() Header {
  return s.headers
}

func (s *stream) Ping() <-chan bool {
  return s.conn.Ping()
}

func (s *stream) Push() (PushWriter, error) {
  return nil, nil
}

func (s *stream) Settings() []*Setting {
  return s.conn.receivedSettings
}

func (s *stream) Write(inputData []byte) (int, error) {
  s.processInput()
  if s.stop {
    return 0, ErrCancelled
  }

  // Dereference the pointer.
  data := make([]byte, len(inputData))
  copy(data, inputData)

  if !s.wroteHeader {
    s.WriteHeader(http.StatusOK)
  }

  written := 0
  for len(data) > MAX_DATA_SIZE {
    n, err := s.flow.Write(data[:MAX_DATA_SIZE])
    if err != nil {
      return written, err
    }
    written += n
    data = data[MAX_DATA_SIZE:]
  }

  n, err := s.flow.Write(data)
  written += n

  return written, err
}

func (s *stream) WriteHeader(code int) {
  if s.wroteHeader {
    log.Println("spdy: Error: Multiple calls to ResponseWriter.WriteHeader.")
    return
  }

  s.wroteHeader = true
  s.responseCode = code

  s.headers.Set(":status", fmt.Sprint(code))
  s.headers.Set(":version", "HTTP/1.1")

  synReply := new(SynReplyFrame)
  synReply.version = uint16(s.version)
  synReply.StreamID = s.streamID
  synReply.Headers = s.headers

  s.output <- synReply
}

func (s *stream) WriteSettings(settings ...*Setting) {
  if settings == nil {
    return
  }

  frame := new(SettingsFrame)
  frame.version = uint16(s.version)
  frame.Settings = settings
  s.output <- frame
}

func (s *stream) receiveFrame(frame Frame) {
  if frame == nil {
    panic("Nil frame received in receiveFrame.")
  }

  switch frame := frame.(type) {
  case *DataFrame:
    s.requestBody.Write(frame.Data)

  case *HeadersFrame:
    s.headers.Update(frame.Headers)

  case *WindowUpdateFrame:
    err := s.flow.UpdateWindow(frame.DeltaWindowSize)
    if err != nil {
      reply := new(RstStreamFrame)
      reply.version = uint16(s.version)
      reply.StreamID = s.streamID
      reply.StatusCode = RST_STREAM_FLOW_CONTROL_ERROR
      s.output <- reply
      return
    }

  default:
    panic(fmt.Sprintf("Received unknown frame of type %T.", frame))
  }
}

func (s *stream) wait() {
  frame := <-s.input
  if frame == nil {
    return
  }
  s.receiveFrame(frame)
}

func (s *stream) processInput() {
  var frame Frame
  var ok bool

  for {
    select {
    case frame, ok = <-s.input:
      if !ok {
        return
      }
      s.receiveFrame(frame)

    default:
      return
    }
  }
}

func (s *stream) run() {

  // Make sure Request is prepared.
  s.AddFlowControl()
  s.requestBody = new(bytes.Buffer)
  s.processInput()
  s.request.Body = &readCloserBuffer{s.requestBody}

  /***************
   *** HANDLER ***
   ***************/
  s.handler.ServeSPDY(s, s.request)

  // Make sure any queued data has been sent.
  for s.flow.Paused() {
    s.wait()
    s.flow.Flush()
  }

  if !s.wroteHeader {
    s.headers.Set(":status", "200")
    s.headers.Set(":version", "HTTP/1.1")

    synReply := new(SynReplyFrame)
    synReply.version = uint16(s.version)
    synReply.Flags = FLAG_FIN
    synReply.StreamID = s.streamID
    synReply.Headers = s.headers

    s.output <- synReply
  } else {
    data := new(DataFrame)
    data.StreamID = s.streamID
    data.Flags = FLAG_FIN
    data.Data = []byte{}

    s.output <- data
  }

  // Clean up state.
  if s.state == STATE_HALF_CLOSED_THERE {
    s.state = STATE_CLOSED
  } else {
    state = STATE_HALF_CLOSED_HERE
  }
  s.conn.done.Done()
}

type readCloserBuffer struct {
  *bytes.Buffer
}

func (_ *readCloserBuffer) Close() error {
  return nil
}
<|endoftext|>


<commit_before>package spdy

import (
  "bytes"
  "fmt"
  "log"
  "net/http"
  "sync"
)

type stream struct {
  sync.RWMutex
  conn           *connection
  streamID       uint32
  requestBody    *bytes.Buffer
  state          StreamState
  input          <-chan Frame
  output         chan<- Frame
  request        *Request
  handler        *ServeMux
  certificates   []Certificate
  headers        Header
  settings       []*Setting
  unidirectional bool
  responseSent   bool
  responseCode   int
  stop           bool
  wroteHeader    bool
  version        int
}

func (s *stream) Header() Header {
  return s.headers
}

func (s *stream) Ping() <-chan bool {
  return make(chan bool)
}

func (s *stream) Push() (PushWriter, error) {
  return nil, nil
}

func (s *stream) Settings() []*Setting {
  return s.conn.receivedSettings
}

func (s *stream) Write(data []byte) (int, error) {
  if s.stop {
    return 0, ErrCancelled
  }

  if !s.wroteHeader {
    s.WriteHeader(http.StatusOK)
  }

  if len(data) == 0 {
    return 0, nil
  }

  dataFrame := new(DataFrame)
  dataFrame.StreamID = s.streamID
  dataFrame.Data = data

  s.output <- dataFrame
  if DebugMode {
    fmt.Printf("Debug: Wrote %d bytes of data from stream %d.\n", len(data), s.streamID)
  }

  return len(data), nil
}

func (s *stream) WriteHeader(code int) {
  if s.wroteHeader {
    log.Println("spdy: Error: Multiple calls to ResponseWriter.WriteHeader.")
    return
  }

  s.wroteHeader = true
  s.responseCode = code

  s.headers.Set(":status", fmt.Sprint(code))
  s.headers.Set(":version", "HTTP/1.1")

  synReply := new(SynReplyFrame)
  synReply.Version = uint16(s.version)
  synReply.StreamID = s.streamID
  synReply.Headers = s.headers

  s.output <- synReply
}

func (s *stream) WriteSettings(settings ...*Setting) {
  if settings == nil {
    return
  }

  frame := new(SettingsFrame)
  frame.Version = uint16(s.version)
  frame.Settings = settings
  s.output <- frame
}

func (s *stream) wait() {
  var frame Frame

  select {
  case frame = <-s.input:
    switch frame := frame.(type) {
    case *DataFrame:
      s.requestBody.Write(frame.Data)

    case *HeadersFrame:
      s.headers.Update(frame.Headers)

    default:
      panic(fmt.Sprintf("Received unknown frame of type %T.", frame))
    }
  }
}

func (s *stream) processInput() {
  var frame Frame

  for {
    select {
    case frame = <-s.input:
      switch frame := frame.(type) {
      case *DataFrame:
        s.requestBody.Write(frame.Data)

      case *HeadersFrame:
        s.headers.Update(frame.Headers)

      default:
        panic(fmt.Sprintf("Received unknown frame of type %T.", frame))
      }

    default:
      return
    }
  }
}

func (s *stream) run() {

  // Make sure Request is prepared.
  s.requestBody = new(bytes.Buffer)
  s.processInput()
  s.request.Body = &readCloserBuffer{body}

  /***************
   *** HANDLER ***
   ***************/
  s.handler.ServeSPDY(s, s.request)

  if !s.wroteHeader {
    s.headers.Set(":status", "200")
    s.headers.Set(":version", "HTTP/1.1")

    synReply := new(SynReplyFrame)
    synReply.Version = uint16(s.version)
    synReply.Flags = FLAG_FIN
    synReply.StreamID = s.streamID
    synReply.Headers = s.headers

    s.output <- synReply
  } else {
    cancel := new(RstStreamFrame)
    cancel.Version = uint16(s.version)
    cancel.StreamID = s.streamID
    cancel.StatusCode = RST_STREAM_CANCEL

    s.output <- cancel
  }

  s.conn.done.Done()
}

type readCloserBuffer struct {
  *bytes.Buffer
}

func (_ *readCloserBuffer) Close() error {
  return nil
}
<commit_msg>Further formatting and structural improvements<commit_after>package spdy

import (
  "bytes"
  "fmt"
  "log"
  "net/http"
  "sync"
)

type stream struct {
  sync.RWMutex
  conn           *connection
  streamID       uint32
  requestBody    *bytes.Buffer
  state          StreamState
  input          <-chan Frame
  output         chan<- Frame
  request        *Request
  handler        *ServeMux
  certificates   []Certificate
  headers        Header
  settings       []*Setting
  unidirectional bool
  responseSent   bool
  responseCode   int
  stop           bool
  wroteHeader    bool
  version        int
}

func (s *stream) Header() Header {
  return s.headers
}

func (s *stream) Ping() <-chan bool {
  return make(chan bool)
}

func (s *stream) Push() (PushWriter, error) {
  return nil, nil
}

func (s *stream) Settings() []*Setting {
  return s.conn.receivedSettings
}

func (s *stream) Write(data []byte) (int, error) {
  if s.stop {
    return 0, ErrCancelled
  }

  if !s.wroteHeader {
    s.WriteHeader(http.StatusOK)
  }

  if len(data) == 0 {
    return 0, nil
  }

  dataFrame := new(DataFrame)
  dataFrame.StreamID = s.streamID
  dataFrame.Data = data

  s.output <- dataFrame
  if DebugMode {
    fmt.Printf("Debug: Wrote %d bytes of data from stream %d.\n", len(data), s.streamID)
  }

  return len(data), nil
}

func (s *stream) WriteHeader(code int) {
  if s.wroteHeader {
    log.Println("spdy: Error: Multiple calls to ResponseWriter.WriteHeader.")
    return
  }

  s.wroteHeader = true
  s.responseCode = code

  s.headers.Set(":status", fmt.Sprint(code))
  s.headers.Set(":version", "HTTP/1.1")

  synReply := new(SynReplyFrame)
  synReply.Version = uint16(s.version)
  synReply.StreamID = s.streamID
  synReply.Headers = s.headers

  s.output <- synReply
}

func (s *stream) WriteSettings(settings ...*Setting) {
  if settings == nil {
    return
  }

  frame := new(SettingsFrame)
  frame.Version = uint16(s.version)
  frame.Settings = settings
  s.output <- frame
}

func (s *stream) receiveFrame(frame Frame) {
  switch frame := frame.(type) {
  case *DataFrame:
    s.requestBody.Write(frame.Data)

  case *HeadersFrame:
    s.headers.Update(frame.Headers)

  default:
    panic(fmt.Sprintf("Received unknown frame of type %T.", frame))
  }
}

func (s *stream) wait() {
  s.receiveFrame(<-s.input)
}

func (s *stream) processInput() {
  var frame Frame

  for {
    select {
    case frame = <-s.input:
      s.receiveFrame(frame)

    default:
      return
    }
  }
}

func (s *stream) run() {

  // Make sure Request is prepared.
  s.requestBody = new(bytes.Buffer)
  s.processInput()
  s.request.Body = &readCloserBuffer{s.requestBody}

  /***************
   *** HANDLER ***
   ***************/
  s.handler.ServeSPDY(s, s.request)

  if !s.wroteHeader {
    s.headers.Set(":status", "200")
    s.headers.Set(":version", "HTTP/1.1")

    synReply := new(SynReplyFrame)
    synReply.Version = uint16(s.version)
    synReply.Flags = FLAG_FIN
    synReply.StreamID = s.streamID
    synReply.Headers = s.headers

    s.output <- synReply
  } else {
    cancel := new(RstStreamFrame)
    cancel.Version = uint16(s.version)
    cancel.StreamID = s.streamID
    cancel.StatusCode = RST_STREAM_CANCEL

    s.output <- cancel
  }

  s.conn.done.Done()
}

type queue struct {
  data []byte
}

func (q *queue) Push(data []byte) {
  if q.data == nil {
    q.data = data
  } else {
    q.data = append(q.data, data...)
  }
}

func (q *queue) Pop(n int) []byte {
  if n < len(q.data) {
    out := q.data[:n]
    q.data = q.data[n:]
    return out
  }

  out := q.data
  q.data = nil
  return out
}

func (q *queue) Empty() bool {
  return len(q.data) == 0
}

type readCloserBuffer struct {
  *bytes.Buffer
}

func (_ *readCloserBuffer) Close() error {
  return nil
}
<|endoftext|>