<commit_before>package renders

import (
	"fmt"
	"html/template"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
)

var (
	cache               []*namedTemplate
	regularTemplateDefs []string
	basePath            string
	exts                []string
	lock                sync.Mutex
	re_defineTag        *regexp.Regexp = regexp.MustCompile("{{ ?define \"([^\"]*)\" ?\"?([a-zA-Z0-9]*)?\"? ?}}")
	re_templateTag      *regexp.Regexp = regexp.MustCompile("{{ ?template \"([^\"]*)\" ?([^ ]*)? ?}}")
)

type namedTemplate struct {
	Name string
	Src  string
}

// Load prepares and parses all templates from the passed basePath
func Load(opt Options) (map[string]*template.Template, error) {
	basePath = opt.Directory
	exts = opt.Extensions
	return loadTemplates(nil)
}

// LoadWithFuncMap prepares and parses all templates from the passed basePath and injects
// a custom template.FuncMap into each template
func LoadWithFuncMap(opt Options) (map[string]*template.Template, error) {
	basePath = opt.Directory
	exts = opt.Extensions
	return loadTemplates(opt.Funcs)
}

func loadTemplates(funcMap template.FuncMap) (map[string]*template.Template, error) {
	lock.Lock()
	defer lock.Unlock()

	templates := make(map[string]*template.Template)

	err := filepath.Walk(basePath, func(path string, fi os.FileInfo, err error) error {
		r, err := filepath.Rel(basePath, path)
		if err != nil {
			return err
		}

		ext := getExt(r)

		for _, extension := range exts {
			if ext == extension {
				if err := add(path); err != nil {
					panic(err)
				}

				// Now we find all regular template definitions and check for the most recent definiton
				for _, t := range regularTemplateDefs {
					found := false
					defineIdx := 0
					// From the beginning (which should) most specifc we look for definitions
					for _, nt := range cache {
						nt.Src = re_defineTag.ReplaceAllStringFunc(nt.Src, func(raw string) string {
							parsed := re_defineTag.FindStringSubmatch(raw)
							name := parsed[1]
							if name != t {
								return raw
							}
							// Don't touch the first definition
							if !found {
								found = true
								return raw
							}

							defineIdx += 1

							return fmt.Sprintf("{{ define \"%s_invalidated_#%d\" }}", name, defineIdx)
						})
					}
				}

				var (
					baseTmpl *template.Template
					i        int
				)

				for _, nt := range cache {
					var currentTmpl *template.Template
					if i == 0 {
						baseTmpl = template.New(nt.Name)
						currentTmpl = baseTmpl
					} else {
						currentTmpl = baseTmpl.New(nt.Name)
					}

					err := template.Must(currentTmpl.Funcs(funcMap).Parse(nt.Src))
					return err
					i++
				}
				tname := generateTemplateName(basePath, path)
				templates[tname] = baseTmpl

				// Make sure we empty the cache between runs
				cache = cache[0:0]

				break
				//return nil
			}
		}
		return nil

	})

	return templates, err
}

func add(path string) error {
	// Get file content
	tplSrc, err := file_content(path)
	if err != nil {
		return err
	}

	tplName := generateTemplateName(basePath, path)

	// Make sure template is not already included
	alreadyIncluded := false
	for _, nt := range cache {
		if nt.Name == tplName {
			alreadyIncluded = true
			break
		}
	}
	if alreadyIncluded {
		return nil
	}

	// Add to the cache
	nt := &namedTemplate{
		Name: tplName,
		Src:  tplSrc,
	}
	cache = append(cache, nt)

	// Check for any template block
	for _, raw := range re_templateTag.FindAllString(nt.Src, -1) {
		parsed := re_templateTag.FindStringSubmatch(raw)
		templatePath := parsed[1]
		ext := getExt(templatePath)
		if !strings.Contains(templatePath, ext) {
			regularTemplateDefs = append(regularTemplateDefs, templatePath)
			continue
		}

		// Add this template and continue looking for more template blocks
		add(filepath.Join(basePath, templatePath))
	}

	return nil
}
<commit_msg>revise template must bug<commit_after>package renders

import (
	"fmt"
	"html/template"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
)

var (
	cache               []*namedTemplate
	regularTemplateDefs []string
	basePath            string
	exts                []string
	lock                sync.Mutex
	re_defineTag        *regexp.Regexp = regexp.MustCompile("{{ ?define \"([^\"]*)\" ?\"?([a-zA-Z0-9]*)?\"? ?}}")
	re_templateTag      *regexp.Regexp = regexp.MustCompile("{{ ?template \"([^\"]*)\" ?([^ ]*)? ?}}")
)

type namedTemplate struct {
	Name string
	Src  string
}

// Load prepares and parses all templates from the passed basePath
func Load(opt Options) (map[string]*template.Template, error) {
	basePath = opt.Directory
	exts = opt.Extensions
	return loadTemplates(nil)
}

// LoadWithFuncMap prepares and parses all templates from the passed basePath and injects
// a custom template.FuncMap into each template
func LoadWithFuncMap(opt Options) (map[string]*template.Template, error) {
	basePath = opt.Directory
	exts = opt.Extensions
	return loadTemplates(opt.Funcs)
}

func loadTemplates(funcMap template.FuncMap) (map[string]*template.Template, error) {
	lock.Lock()
	defer lock.Unlock()

	templates := make(map[string]*template.Template)

	err := filepath.Walk(basePath, func(path string, fi os.FileInfo, err error) error {
		r, err := filepath.Rel(basePath, path)
		if err != nil {
			return err
		}

		ext := getExt(r)

		for _, extension := range exts {
			if ext == extension {
				if err := add(path); err != nil {
					panic(err)
				}

				// Now we find all regular template definitions and check for the most recent definiton
				for _, t := range regularTemplateDefs {
					found := false
					defineIdx := 0
					// From the beginning (which should) most specifc we look for definitions
					for _, nt := range cache {
						nt.Src = re_defineTag.ReplaceAllStringFunc(nt.Src, func(raw string) string {
							parsed := re_defineTag.FindStringSubmatch(raw)
							name := parsed[1]
							if name != t {
								return raw
							}
							// Don't touch the first definition
							if !found {
								found = true
								return raw
							}

							defineIdx += 1

							return fmt.Sprintf("{{ define \"%s_invalidated_#%d\" }}", name, defineIdx)
						})
					}
				}

				var (
					baseTmpl *template.Template
					i        int
				)

				for _, nt := range cache {
					var currentTmpl *template.Template
					if i == 0 {
						baseTmpl = template.New(nt.Name)
						currentTmpl = baseTmpl
					} else {
						currentTmpl = baseTmpl.New(nt.Name)
					}

					//template.Must(currentTmpl.Funcs(funcMap).Parse(nt.Src))
					currentTmpl.Funcs(funcMap)
					if _, err := currentTmpl.Parse(nt.Src); err != nil {
						return err
					}
					i++
				}
				tname := generateTemplateName(basePath, path)
				templates[tname] = baseTmpl

				// Make sure we empty the cache between runs
				cache = cache[0:0]

				break
				//return nil
			}
		}
		return nil

	})

	return templates, err
}

func add(path string) error {
	// Get file content
	tplSrc, err := file_content(path)
	if err != nil {
		return err
	}

	tplName := generateTemplateName(basePath, path)

	// Make sure template is not already included
	alreadyIncluded := false
	for _, nt := range cache {
		if nt.Name == tplName {
			alreadyIncluded = true
			break
		}
	}
	if alreadyIncluded {
		return nil
	}

	// Add to the cache
	nt := &namedTemplate{
		Name: tplName,
		Src:  tplSrc,
	}
	cache = append(cache, nt)

	// Check for any template block
	for _, raw := range re_templateTag.FindAllString(nt.Src, -1) {
		parsed := re_templateTag.FindStringSubmatch(raw)
		templatePath := parsed[1]
		ext := getExt(templatePath)
		if !strings.Contains(templatePath, ext) {
			regularTemplateDefs = append(regularTemplateDefs, templatePath)
			continue
		}

		// Add this template and continue looking for more template blocks
		add(filepath.Join(basePath, templatePath))
	}

	return nil
}
<|endoftext|>


<commit_before>package integration_test

import (
	"io/ioutil"
	"net/http"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("API V1.0", func() {
	BeforeEach(clearApparatchik)

	Describe("GET /api/v1.0/applications", func() {
		FContext("When there are no applications defined", func() {
			var response *http.Response
			BeforeEach(func() {
				var err error
				response, err = http.Get("http://localhost:12080/api/v1.0/applications")
				Expect(err).ToNot(HaveOccurred())
			})

			AfterEach(func() {
				Expect(response.Body.Close()).To(Succeed())
			})

			It("Should return 200 status code", func() {
				Expect(response.StatusCode).To(Equal(200))
			})

			It("Should return application/json content type", func() {
				Expect(response.Header.Get("Content-Type")).To(Equal("application/json"))
			})

			It("Should return empty JSON array", func() {
				data, err := ioutil.ReadAll(response.Body)
				Expect(err).ToNot(HaveOccurred())
				Expect(data).To(MatchJSON("[]"))
			})
		})

	})
})
<commit_msg>remove focus from api<commit_after>package integration_test

import (
	"io/ioutil"
	"net/http"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("API V1.0", func() {
	BeforeEach(clearApparatchik)

	Describe("GET /api/v1.0/applications", func() {
		Context("When there are no applications defined", func() {
			var response *http.Response
			BeforeEach(func() {
				var err error
				response, err = http.Get("http://localhost:12080/api/v1.0/applications")
				Expect(err).ToNot(HaveOccurred())
			})

			AfterEach(func() {
				Expect(response.Body.Close()).To(Succeed())
			})

			It("Should return 200 status code", func() {
				Expect(response.StatusCode).To(Equal(200))
			})

			It("Should return application/json content type", func() {
				Expect(response.Header.Get("Content-Type")).To(Equal("application/json"))
			})

			It("Should return empty JSON array", func() {
				data, err := ioutil.ReadAll(response.Body)
				Expect(err).ToNot(HaveOccurred())
				Expect(data).To(MatchJSON("[]"))
			})
		})

	})
})
<|endoftext|>


<commit_before>package agent

import (
	"context"
	"fmt"
	"time"

	"github.com/coreos/go-systemd/login1"
	"github.com/golang/glog"
	"k8s.io/client-go/kubernetes"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	v1api "k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/fields"
	"k8s.io/client-go/pkg/util/wait"
	"k8s.io/client-go/pkg/watch"

	"github.com/coreos-inc/container-linux-update-operator/internal/constants"
	"github.com/coreos-inc/container-linux-update-operator/internal/drain"
	"github.com/coreos-inc/container-linux-update-operator/internal/k8sutil"
	"github.com/coreos-inc/container-linux-update-operator/internal/updateengine"
)

type Klocksmith struct {
	node string
	kc   *kubernetes.Clientset
	nc   v1core.NodeInterface
	ue   *updateengine.Client
	lc   *login1.Conn
}

var (
	shouldRebootSelector = fields.Set(map[string]string{
		constants.AnnotationOkToReboot:   constants.True,
		constants.AnnotationRebootNeeded: constants.True,
	}).AsSelector()

	shouldntRebootSelector = fields.OneTermEqualSelector(constants.AnnotationOkToReboot, constants.False)
)

func New(node string) (*Klocksmith, error) {
	// set up kubernetes in-cluster client
	kc, err := k8sutil.InClusterClient()
	if err != nil {
		return nil, fmt.Errorf("error creating kubernetes client: %v", err)
	}

	// node interface
	nc := kc.Nodes()

	// set up update_engine client
	ue, err := updateengine.New()
	if err != nil {
		return nil, fmt.Errorf("error establishing connection to update_engine dbus: %v", err)
	}

	// set up login1 client for our eventual reboot
	lc, err := login1.New()
	if err != nil {
		return nil, fmt.Errorf("error establishing connection to logind dbus: %v", err)
	}

	return &Klocksmith{node, kc, nc, ue, lc}, nil
}

// updateStatusCallback receives Status messages from update engine. If the
// status is UpdateStatusUpdatedNeedReboot, indicate that with a label on our
// node.
func (k *Klocksmith) updateStatusCallback(s updateengine.Status) {
	glog.Info("Updating status")
	// update our status
	anno := map[string]string{
		constants.AnnotationStatus:          s.CurrentOperation,
		constants.AnnotationLastCheckedTime: fmt.Sprintf("%d", s.LastCheckedTime),
		constants.AnnotationNewVersion:      s.NewVersion,
	}

	// indicate we need a reboot
	if s.CurrentOperation == updateengine.UpdateStatusUpdatedNeedReboot {
		glog.Info("Indicating a reboot is needed")
		anno[constants.AnnotationRebootNeeded] = constants.True
	}

	wait.PollUntil(10*time.Second, func() (bool, error) {
		if err := k8sutil.SetNodeAnnotations(k.nc, k.node, anno); err != nil {
			glog.Errorf("Failed to set annotation %q: %v", constants.AnnotationStatus, err)
			return false, nil
		}

		return true, nil
	}, wait.NeverStop)
}

// setInfoLabels labels our node with helpful info about Container Linux.
func (k *Klocksmith) setInfoLabels() error {
	vi, err := k8sutil.GetVersionInfo()
	if err != nil {
		return fmt.Errorf("failed to get version info: %v", err)
	}

	labels := map[string]string{
		constants.LabelID:      vi.ID,
		constants.LabelGroup:   vi.Group,
		constants.LabelVersion: vi.Version,
	}

	if err := k8sutil.SetNodeLabels(k.nc, k.node, labels); err != nil {
		return err
	}

	return nil
}

// Run runs klocksmithd, reacting to the update_engine reboot signal by
// draining pods on this kubernetes node and rebooting.
//
// TODO(mischief): try to be more resilient against transient failures
func (k *Klocksmith) Run() error {
	glog.Info("Setting info labels")
	if err := k.setInfoLabels(); err != nil {
		return fmt.Errorf("failed to set node info: %v", err)
	}

	// we are schedulable now.
	glog.Info("Marking node as schedulable")
	if err := k8sutil.Unschedulable(k.nc, k.node, false); err != nil {
		return err
	}

	// set coreos.com/update1/reboot-in-progress=false and
	// coreos.com/update1/reboot-needed=false
	anno := map[string]string{
		constants.AnnotationRebootInProgress: constants.False,
		constants.AnnotationRebootNeeded:     constants.False,
	}
	glog.Infof("Setting annotations %#v", anno)
	if err := k8sutil.SetNodeAnnotations(k.nc, k.node, anno); err != nil {
		return err
	}
	// Since we set 'reboot-needed=false', 'ok-to-reboot' should clear.
	// Wait for it to do so, else we might start reboot-looping
	if err := k.waitForNotOkToReboot(); err != nil {
		return err
	}

	// watch update engine for status updates
	go k.watchUpdateStatus(k.updateStatusCallback)

	// block until constants.AnnotationOkToReboot is set
	glog.Infof("Waiting for ok-to-reboot from controller...")
	if err := k.waitForOkToReboot(); err != nil {
		return err
	}

	// set constants.AnnotationRebootInProgress and drain self
	anno = map[string]string{
		constants.AnnotationRebootInProgress: constants.True,
	}

	glog.Infof("Setting annotations %#v", anno)
	if err := k8sutil.SetNodeAnnotations(k.nc, k.node, anno); err != nil {
		return err
	}

	// drain self equates to:
	// 1. set Unschedulable
	// 2. delete all pods
	// unlike `kubectl drain`, we do not care about emptyDir or orphan pods
	// ('any pods that are neither mirror pods nor managed by
	// ReplicationController, ReplicaSet, DaemonSet or Job')

	glog.Info("Marking node as unschedulable")
	if err := k8sutil.Unschedulable(k.nc, k.node, true); err != nil {
		return err
	}

	glog.Info("Getting pod list for deletion")
	pods, err := k.getPodsForDeletion()
	if err != nil {
		return err
	}

	// delete the pods.
	// TODO(mischief): explicitly don't terminate self? we'll probably just be a
	// mirror pod or daemonset anyway..
	glog.Infof("Deleting %d pods", len(pods))
	deleteOptions := v1api.NewDeleteOptions(30)
	for _, pod := range pods {
		glog.Infof("Terminating pod %q...", pod.Name)
		if err := k.kc.Pods(pod.Namespace).Delete(pod.Name, deleteOptions); err != nil {
			return fmt.Errorf("failed terminating pod %q: %v", pod.Name, err)
		}
	}

	glog.Info("Node drained, rebooting")

	// reboot
	k.lc.Reboot(false)

	// cross fingers
	time.Sleep(24 * 7 * time.Hour)

	return nil
}

func (k *Klocksmith) watchUpdateStatus(update func(s updateengine.Status)) {
	glog.Info("Beginning to watch update_engine status")

	oldOperation := ""
	ch := make(chan updateengine.Status, 1)
	stop := make(chan struct{})

	go k.ue.ReceiveStatuses(ch, stop)

	// synthesize first message
	st, err := k.ue.GetStatus()
	if err != nil {
		glog.Fatalf("Failed to get update_engine status: %v", err)
	}

	ch <- st

	for status := range ch {
		if status.CurrentOperation != oldOperation && update != nil {
			update(status)
			oldOperation = status.CurrentOperation
		}
	}
}

// waitForRebootSignal watches update_engine and waits for
// updateengine.UpdateStatusUpdatedNeedReboot
func (k *Klocksmith) waitForRebootSignal() error {
	// first, check current status.
	status, err := k.ue.GetStatus()
	if err != nil {
		return fmt.Errorf("failed to get update_engine status: %v", err)
	}

	// if we're not in UpdateStatusUpdatedNeedReboot, then wait for it.
	if status.CurrentOperation != updateengine.UpdateStatusUpdatedNeedReboot {
		ctx, cancel := context.WithCancel(context.Background())
		stc := make(chan updateengine.Status)
		go k.ue.RebootNeededSignal(stc, ctx.Done())
		<-stc
		cancel()
	}

	// we are now in UpdateStatusUpdatedNeedReboot state.
	return nil
}

// waitForOkToReboot waits for both 'ok-to-reboot' and 'needs-reboot' to be true.
func (k *Klocksmith) waitForOkToReboot() error {
	n, err := k.nc.Get(k.node)
	if err != nil {
		return fmt.Errorf("failed to get self node (%q): %v", k.node, err)
	}

	if n.Annotations[constants.AnnotationOkToReboot] == constants.True && n.Annotations[constants.AnnotationRebootNeeded] == constants.True {
		return nil
	}

	// XXX: set timeout > 0?
	watcher, err := k.nc.Watch(v1api.ListOptions{
		FieldSelector:   fields.OneTermEqualSelector("metadata.name", n.Name).String(),
		ResourceVersion: n.ResourceVersion,
	})
	if err != nil {
		return fmt.Errorf("failed to watch self node (%q): %v", k.node, err)
	}

	// hopefully 24 hours is enough time between indicating we need a
	// reboot and the controller telling us to do it
	ev, err := watch.Until(time.Hour*24, watcher, k8sutil.NodeAnnotationCondition(shouldRebootSelector))
	if err != nil {
		return fmt.Errorf("waiting for annotation %q failed: %v", constants.AnnotationOkToReboot, err)
	}

	// sanity check
	no, ok := ev.Object.(*v1api.Node)
	if !ok {
		panic("event contains a non-*api.Node object")
	}

	if no.Annotations[constants.AnnotationOkToReboot] != constants.True {
		panic("event did not contain annotation expected")
	}

	return nil
}

func (k *Klocksmith) waitForNotOkToReboot() error {
	n, err := k.nc.Get(k.node)
	if err != nil {
		return fmt.Errorf("failed to get self node (%q): %v", k.node, err)
	}

	if n.Annotations[constants.AnnotationOkToReboot] != constants.True {
		return nil
	}

	// XXX: set timeout > 0?
	watcher, err := k.nc.Watch(v1api.ListOptions{
		FieldSelector:   fields.OneTermEqualSelector("metadata.name", n.Name).String(),
		ResourceVersion: n.ResourceVersion,
	})
	if err != nil {
		return fmt.Errorf("failed to watch self node (%q): %v", k.node, err)
	}

	// Within 24 hours of indicating we don't need a reboot we should be given a not-ok.
	// If that isn't the case, it likely means the operator isn't running, and
	// we'll just crash-loop in that case, and hopefully that will help the user realize something's wrong.
	ev, err := watch.Until(time.Hour*24, watcher, k8sutil.NodeAnnotationCondition(shouldntRebootSelector))
	if err != nil {
		return fmt.Errorf("waiting for annotation %q failed: %v", constants.AnnotationOkToReboot, err)
	}

	// sanity check
	no, ok := ev.Object.(*v1api.Node)
	if !ok {
		panic("event contains a non-*api.Node object")
	}

	if no.Annotations[constants.AnnotationOkToReboot] == constants.True {
		panic("event did not contain annotation expected")
	}

	return nil
}

func (k *Klocksmith) getPodsForDeletion() ([]v1api.Pod, error) {
	pods, err := drain.GetPodsForDeletion(k.kc, k.node)
	if err != nil {
		return nil, fmt.Errorf("failed to get list of pods for deletion: %v", err)
	}

	// XXX: ignoring kube-system is a simple way to avoid eviciting
	// critical components such as kube-scheduler and
	// kube-controller-manager.

	pods = k8sutil.FilterPods(pods, func(p *v1api.Pod) bool {
		if p.Namespace == "kube-system" {
			return false
		}

		return true
	})

	return pods, nil
}
<commit_msg>agent: make 'not-ok-to-reboot' check more robust<commit_after>package agent

import (
	"context"
	"fmt"
	"time"

	"github.com/coreos/go-systemd/login1"
	"github.com/golang/glog"
	"k8s.io/client-go/kubernetes"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
	v1api "k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/fields"
	"k8s.io/client-go/pkg/util/wait"
	"k8s.io/client-go/pkg/watch"

	"github.com/coreos-inc/container-linux-update-operator/internal/constants"
	"github.com/coreos-inc/container-linux-update-operator/internal/drain"
	"github.com/coreos-inc/container-linux-update-operator/internal/k8sutil"
	"github.com/coreos-inc/container-linux-update-operator/internal/updateengine"
)

type Klocksmith struct {
	node string
	kc   *kubernetes.Clientset
	nc   v1core.NodeInterface
	ue   *updateengine.Client
	lc   *login1.Conn
}

var (
	shouldRebootSelector = fields.Set(map[string]string{
		constants.AnnotationOkToReboot:   constants.True,
		constants.AnnotationRebootNeeded: constants.True,
	}).AsSelector()
)

func New(node string) (*Klocksmith, error) {
	// set up kubernetes in-cluster client
	kc, err := k8sutil.InClusterClient()
	if err != nil {
		return nil, fmt.Errorf("error creating kubernetes client: %v", err)
	}

	// node interface
	nc := kc.Nodes()

	// set up update_engine client
	ue, err := updateengine.New()
	if err != nil {
		return nil, fmt.Errorf("error establishing connection to update_engine dbus: %v", err)
	}

	// set up login1 client for our eventual reboot
	lc, err := login1.New()
	if err != nil {
		return nil, fmt.Errorf("error establishing connection to logind dbus: %v", err)
	}

	return &Klocksmith{node, kc, nc, ue, lc}, nil
}

// updateStatusCallback receives Status messages from update engine. If the
// status is UpdateStatusUpdatedNeedReboot, indicate that with a label on our
// node.
func (k *Klocksmith) updateStatusCallback(s updateengine.Status) {
	glog.Info("Updating status")
	// update our status
	anno := map[string]string{
		constants.AnnotationStatus:          s.CurrentOperation,
		constants.AnnotationLastCheckedTime: fmt.Sprintf("%d", s.LastCheckedTime),
		constants.AnnotationNewVersion:      s.NewVersion,
	}

	// indicate we need a reboot
	if s.CurrentOperation == updateengine.UpdateStatusUpdatedNeedReboot {
		glog.Info("Indicating a reboot is needed")
		anno[constants.AnnotationRebootNeeded] = constants.True
	}

	wait.PollUntil(10*time.Second, func() (bool, error) {
		if err := k8sutil.SetNodeAnnotations(k.nc, k.node, anno); err != nil {
			glog.Errorf("Failed to set annotation %q: %v", constants.AnnotationStatus, err)
			return false, nil
		}

		return true, nil
	}, wait.NeverStop)
}

// setInfoLabels labels our node with helpful info about Container Linux.
func (k *Klocksmith) setInfoLabels() error {
	vi, err := k8sutil.GetVersionInfo()
	if err != nil {
		return fmt.Errorf("failed to get version info: %v", err)
	}

	labels := map[string]string{
		constants.LabelID:      vi.ID,
		constants.LabelGroup:   vi.Group,
		constants.LabelVersion: vi.Version,
	}

	if err := k8sutil.SetNodeLabels(k.nc, k.node, labels); err != nil {
		return err
	}

	return nil
}

// Run runs klocksmithd, reacting to the update_engine reboot signal by
// draining pods on this kubernetes node and rebooting.
//
// TODO(mischief): try to be more resilient against transient failures
func (k *Klocksmith) Run() error {
	glog.Info("Setting info labels")
	if err := k.setInfoLabels(); err != nil {
		return fmt.Errorf("failed to set node info: %v", err)
	}

	// we are schedulable now.
	glog.Info("Marking node as schedulable")
	if err := k8sutil.Unschedulable(k.nc, k.node, false); err != nil {
		return err
	}

	// set coreos.com/update1/reboot-in-progress=false and
	// coreos.com/update1/reboot-needed=false
	anno := map[string]string{
		constants.AnnotationRebootInProgress: constants.False,
		constants.AnnotationRebootNeeded:     constants.False,
	}
	glog.Infof("Setting annotations %#v", anno)
	if err := k8sutil.SetNodeAnnotations(k.nc, k.node, anno); err != nil {
		return err
	}
	// Since we set 'reboot-needed=false', 'ok-to-reboot' should clear.
	// Wait for it to do so, else we might start reboot-looping
	if err := k.waitForNotOkToReboot(); err != nil {
		return err
	}

	// watch update engine for status updates
	go k.watchUpdateStatus(k.updateStatusCallback)

	// block until constants.AnnotationOkToReboot is set
	glog.Infof("Waiting for ok-to-reboot from controller...")
	if err := k.waitForOkToReboot(); err != nil {
		return err
	}

	// set constants.AnnotationRebootInProgress and drain self
	anno = map[string]string{
		constants.AnnotationRebootInProgress: constants.True,
	}

	glog.Infof("Setting annotations %#v", anno)
	if err := k8sutil.SetNodeAnnotations(k.nc, k.node, anno); err != nil {
		return err
	}

	// drain self equates to:
	// 1. set Unschedulable
	// 2. delete all pods
	// unlike `kubectl drain`, we do not care about emptyDir or orphan pods
	// ('any pods that are neither mirror pods nor managed by
	// ReplicationController, ReplicaSet, DaemonSet or Job')

	glog.Info("Marking node as unschedulable")
	if err := k8sutil.Unschedulable(k.nc, k.node, true); err != nil {
		return err
	}

	glog.Info("Getting pod list for deletion")
	pods, err := k.getPodsForDeletion()
	if err != nil {
		return err
	}

	// delete the pods.
	// TODO(mischief): explicitly don't terminate self? we'll probably just be a
	// mirror pod or daemonset anyway..
	glog.Infof("Deleting %d pods", len(pods))
	deleteOptions := v1api.NewDeleteOptions(30)
	for _, pod := range pods {
		glog.Infof("Terminating pod %q...", pod.Name)
		if err := k.kc.Pods(pod.Namespace).Delete(pod.Name, deleteOptions); err != nil {
			return fmt.Errorf("failed terminating pod %q: %v", pod.Name, err)
		}
	}

	glog.Info("Node drained, rebooting")

	// reboot
	k.lc.Reboot(false)

	// cross fingers
	time.Sleep(24 * 7 * time.Hour)

	return nil
}

func (k *Klocksmith) watchUpdateStatus(update func(s updateengine.Status)) {
	glog.Info("Beginning to watch update_engine status")

	oldOperation := ""
	ch := make(chan updateengine.Status, 1)
	stop := make(chan struct{})

	go k.ue.ReceiveStatuses(ch, stop)

	// synthesize first message
	st, err := k.ue.GetStatus()
	if err != nil {
		glog.Fatalf("Failed to get update_engine status: %v", err)
	}

	ch <- st

	for status := range ch {
		if status.CurrentOperation != oldOperation && update != nil {
			update(status)
			oldOperation = status.CurrentOperation
		}
	}
}

// waitForRebootSignal watches update_engine and waits for
// updateengine.UpdateStatusUpdatedNeedReboot
func (k *Klocksmith) waitForRebootSignal() error {
	// first, check current status.
	status, err := k.ue.GetStatus()
	if err != nil {
		return fmt.Errorf("failed to get update_engine status: %v", err)
	}

	// if we're not in UpdateStatusUpdatedNeedReboot, then wait for it.
	if status.CurrentOperation != updateengine.UpdateStatusUpdatedNeedReboot {
		ctx, cancel := context.WithCancel(context.Background())
		stc := make(chan updateengine.Status)
		go k.ue.RebootNeededSignal(stc, ctx.Done())
		<-stc
		cancel()
	}

	// we are now in UpdateStatusUpdatedNeedReboot state.
	return nil
}

// waitForOkToReboot waits for both 'ok-to-reboot' and 'needs-reboot' to be true.
func (k *Klocksmith) waitForOkToReboot() error {
	n, err := k.nc.Get(k.node)
	if err != nil {
		return fmt.Errorf("failed to get self node (%q): %v", k.node, err)
	}

	if n.Annotations[constants.AnnotationOkToReboot] == constants.True && n.Annotations[constants.AnnotationRebootNeeded] == constants.True {
		return nil
	}

	// XXX: set timeout > 0?
	watcher, err := k.nc.Watch(v1api.ListOptions{
		FieldSelector:   fields.OneTermEqualSelector("metadata.name", n.Name).String(),
		ResourceVersion: n.ResourceVersion,
	})
	if err != nil {
		return fmt.Errorf("failed to watch self node (%q): %v", k.node, err)
	}

	// hopefully 24 hours is enough time between indicating we need a
	// reboot and the controller telling us to do it
	ev, err := watch.Until(time.Hour*24, watcher, k8sutil.NodeAnnotationCondition(shouldRebootSelector))
	if err != nil {
		return fmt.Errorf("waiting for annotation %q failed: %v", constants.AnnotationOkToReboot, err)
	}

	// sanity check
	no, ok := ev.Object.(*v1api.Node)
	if !ok {
		panic("event contains a non-*api.Node object")
	}

	if no.Annotations[constants.AnnotationOkToReboot] != constants.True {
		panic("event did not contain annotation expected")
	}

	return nil
}

func (k *Klocksmith) waitForNotOkToReboot() error {
	n, err := k.nc.Get(k.node)
	if err != nil {
		return fmt.Errorf("failed to get self node (%q): %v", k.node, err)
	}

	if n.Annotations[constants.AnnotationOkToReboot] != constants.True {
		return nil
	}

	// XXX: set timeout > 0?
	watcher, err := k.nc.Watch(v1api.ListOptions{
		FieldSelector:   fields.OneTermEqualSelector("metadata.name", n.Name).String(),
		ResourceVersion: n.ResourceVersion,
	})
	if err != nil {
		return fmt.Errorf("failed to watch self node (%q): %v", k.node, err)
	}

	// Within 24 hours of indicating we don't need a reboot we should be given a not-ok.
	// If that isn't the case, it likely means the operator isn't running, and
	// we'll just crash-loop in that case, and hopefully that will help the user realize something's wrong.
	// Use a custom condition function to use the more correct 'OkToReboot !=
	// true' vs '== False'; due to the operator matching on '== True', and not
	// going out of its way to convert '' => 'False', checking the exact inverse
	// of what the operator checks is the correct thing to do.
	ev, err := watch.Until(time.Hour*24, watcher, watch.ConditionFunc(func(event watch.Event) (bool, error) {
		switch event.Type {
		case watch.Error:
			return false, fmt.Errorf("error watching node: %v", event.Object)
		case watch.Deleted:
			return false, fmt.Errorf("our node was deleted while we were waiting for ready")
		}

		no := event.Object.(*v1api.Node)
		if no.Annotations[constants.AnnotationOkToReboot] != constants.True {
			return true, nil
		}
		return false, nil
	}))
	if err != nil {
		return fmt.Errorf("waiting for annotation %q failed: %v", constants.AnnotationOkToReboot, err)
	}

	// sanity check
	no := ev.Object.(*v1api.Node)

	if no.Annotations[constants.AnnotationOkToReboot] == constants.True {
		panic("event did not contain annotation expected")
	}

	return nil
}

func (k *Klocksmith) getPodsForDeletion() ([]v1api.Pod, error) {
	pods, err := drain.GetPodsForDeletion(k.kc, k.node)
	if err != nil {
		return nil, fmt.Errorf("failed to get list of pods for deletion: %v", err)
	}

	// XXX: ignoring kube-system is a simple way to avoid eviciting
	// critical components such as kube-scheduler and
	// kube-controller-manager.

	pods = k8sutil.FilterPods(pods, func(p *v1api.Pod) bool {
		if p.Namespace == "kube-system" {
			return false
		}

		return true
	})

	return pods, nil
}
<|endoftext|>


<commit_before>package handler

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

	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/cmd"
	"github.com/micro/go-micro/errors"
	"github.com/micro/micro/internal/helper"
	"time"
)

type rpcRequest struct {
	Service string
	Method  string
	Address string
	Request interface{}
}

// RPC Handler passes on a JSON or form encoded RPC request to
// a service.
func RPC(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	badRequest := func(description string) {
		e := errors.BadRequest("go.micro.rpc", description)
		w.WriteHeader(400)
		w.Write([]byte(e.Error()))
	}

	var service, method, address string
	var request interface{}

	// response content type
	w.Header().Set("Content-Type", "application/json")

	ct := r.Header.Get("Content-Type")

	// Strip charset from Content-Type (like `application/json; charset=UTF-8`)
	if idx := strings.IndexRune(ct, ';'); idx >= 0 {
		ct = ct[:idx]
	}

	switch ct {
	case "application/json":
		var rpcReq rpcRequest

		d := json.NewDecoder(r.Body)
		d.UseNumber()

		if err := d.Decode(&rpcReq); err != nil {
			badRequest(err.Error())
			return
		}

		service = rpcReq.Service
		method = rpcReq.Method
		address = rpcReq.Address
		request = rpcReq.Request

		// JSON as string
		if req, ok := rpcReq.Request.(string); ok {
			d := json.NewDecoder(strings.NewReader(req))
			d.UseNumber()

			if err := d.Decode(&request); err != nil {
				badRequest("error decoding request string: " + err.Error())
				return
			}
		}
	default:
		r.ParseForm()
		service = r.Form.Get("service")
		method = r.Form.Get("method")
		address = r.Form.Get("address")

		d := json.NewDecoder(strings.NewReader(r.Form.Get("request")))
		d.UseNumber()

		if err := d.Decode(&request); err != nil {
			badRequest("error decoding request string: " + err.Error())
			return
		}
	}

	if len(service) == 0 {
		badRequest("invalid service")
		return
	}

	if len(method) == 0 {
		badRequest("invalid method")
		return
	}

	// create request/response
	var response json.RawMessage
	var err error
	req := (*cmd.DefaultOptions().Client).NewRequest(service, method, request, client.WithContentType("application/json"))

	// create context
	ctx := helper.RequestToContext(r)

	var opts []client.CallOption

	timeout, _ := strconv.Atoi(r.Header.Get("Timeout"))
	// set timeout
	if timeout > 0 {
		opts = append(opts, client.WithRequestTimeout(time.Duration(timeout)*time.Second))
	}

	// remote call
	if len(address) > 0 {
		opts = append(opts, client.WithAddress(address))
	}

	// remote call
	err = (*cmd.DefaultOptions().Client).Call(ctx, req, &response, opts...)
	if err != nil {
		ce := errors.Parse(err.Error())
		switch ce.Code {
		case 0:
			// assuming it's totally screwed
			ce.Code = 500
			ce.Id = "go.micro.rpc"
			ce.Status = http.StatusText(500)
			ce.Detail = "error during request: " + ce.Detail
			w.WriteHeader(500)
		default:
			w.WriteHeader(int(ce.Code))
		}
		w.Write([]byte(ce.Error()))
		return
	}

	b, _ := response.MarshalJSON()
	w.Header().Set("Content-Length", strconv.Itoa(len(b)))
	w.Write(b)
}
<commit_msg>styling: move `time` with other stdlibs<commit_after>package handler

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

	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/cmd"
	"github.com/micro/go-micro/errors"
	"github.com/micro/micro/internal/helper"
)

type rpcRequest struct {
	Service string
	Method  string
	Address string
	Request interface{}
}

// RPC Handler passes on a JSON or form encoded RPC request to
// a service.
func RPC(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	badRequest := func(description string) {
		e := errors.BadRequest("go.micro.rpc", description)
		w.WriteHeader(400)
		w.Write([]byte(e.Error()))
	}

	var service, method, address string
	var request interface{}

	// response content type
	w.Header().Set("Content-Type", "application/json")

	ct := r.Header.Get("Content-Type")

	// Strip charset from Content-Type (like `application/json; charset=UTF-8`)
	if idx := strings.IndexRune(ct, ';'); idx >= 0 {
		ct = ct[:idx]
	}

	switch ct {
	case "application/json":
		var rpcReq rpcRequest

		d := json.NewDecoder(r.Body)
		d.UseNumber()

		if err := d.Decode(&rpcReq); err != nil {
			badRequest(err.Error())
			return
		}

		service = rpcReq.Service
		method = rpcReq.Method
		address = rpcReq.Address
		request = rpcReq.Request

		// JSON as string
		if req, ok := rpcReq.Request.(string); ok {
			d := json.NewDecoder(strings.NewReader(req))
			d.UseNumber()

			if err := d.Decode(&request); err != nil {
				badRequest("error decoding request string: " + err.Error())
				return
			}
		}
	default:
		r.ParseForm()
		service = r.Form.Get("service")
		method = r.Form.Get("method")
		address = r.Form.Get("address")

		d := json.NewDecoder(strings.NewReader(r.Form.Get("request")))
		d.UseNumber()

		if err := d.Decode(&request); err != nil {
			badRequest("error decoding request string: " + err.Error())
			return
		}
	}

	if len(service) == 0 {
		badRequest("invalid service")
		return
	}

	if len(method) == 0 {
		badRequest("invalid method")
		return
	}

	// create request/response
	var response json.RawMessage
	var err error
	req := (*cmd.DefaultOptions().Client).NewRequest(service, method, request, client.WithContentType("application/json"))

	// create context
	ctx := helper.RequestToContext(r)

	var opts []client.CallOption

	timeout, _ := strconv.Atoi(r.Header.Get("Timeout"))
	// set timeout
	if timeout > 0 {
		opts = append(opts, client.WithRequestTimeout(time.Duration(timeout)*time.Second))
	}

	// remote call
	if len(address) > 0 {
		opts = append(opts, client.WithAddress(address))
	}

	// remote call
	err = (*cmd.DefaultOptions().Client).Call(ctx, req, &response, opts...)
	if err != nil {
		ce := errors.Parse(err.Error())
		switch ce.Code {
		case 0:
			// assuming it's totally screwed
			ce.Code = 500
			ce.Id = "go.micro.rpc"
			ce.Status = http.StatusText(500)
			ce.Detail = "error during request: " + ce.Detail
			w.WriteHeader(500)
		default:
			w.WriteHeader(int(ce.Code))
		}
		w.Write([]byte(ce.Error()))
		return
	}

	b, _ := response.MarshalJSON()
	w.Header().Set("Content-Length", strconv.Itoa(len(b)))
	w.Write(b)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"http"
	"time"
	"strings"
	"os"
	"net"
	"bufio"
	"sort"
	)

var reqs *int = flag.Int("n", 1, "number of requests to make")
var concurrency *int = flag.Int("c", 1, "concurrency")

type someError struct {
	what string
	str  string
}

func (e *someError) String() string {
	return fmt.Sprintf("%s %q", e.what, e.str)
}

type result struct {
	time int64
	err os.Error
}

type Int64Array []int64
func (p Int64Array) Len() int           { return len(p) }
func (p Int64Array) Less(i, j int) bool { return p[i] < p[j] }
func (p Int64Array) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func main() {
	flag.Parse()

	if len(flag.Args()) == 0 {
		flag.Usage()
		return
	}

	url, err := getURL(flag.Arg(0))
	if err != nil {
		fmt.Printf("url is invalid: %s", err)
		return
	}

	ch := make(chan result, *reqs)
	results := make([]result, *reqs)
	running, i, j := 0, 0, 0

	now := time.Nanoseconds()
	for {
		if running < *concurrency && i < *reqs {
			go send(url, ch)
			running++
			i++
		} else if j < *reqs {
			results[j] = <- ch
			j++
			running--
		}

		if i == j && j >= *reqs {
			break
		}
	}

	printStats(results, time.Nanoseconds() - now)
}

func printStats(results []result, workTime int64) {
	times := make(Int64Array, 0)
	total := int64(0)

	for _, r := range(results) {
		if r.err == nil {
			times = append(times, r.time)
			total += r.time
		}
	}
	sort.Sort(times)

	fmt.Printf(`Statistics for request to %s

Time taken for tests:           %.3f ms
Average request takes:          %.3f ms
Median request time:            %.3f ms
Average time between responses: %.3f ms
Total failures:                 %d
`,
		flag.Arg(0),
		ms(workTime),
		ms(total / int64(len(times))),
		ms(times[len(times) / 2]),
		ms(workTime / int64(*reqs)),
		*reqs - len(times))
}

func ms(x int64) (float64) {
	return float64(x) / 1000000
}

func hasPort(s string) bool {
	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}

func getURL(url string) (*http.URL, os.Error) {
	URL, err := http.ParseURL(url)
	if err != nil {
		return nil, err
	}

	if URL.Scheme != "http" {
		return nil, &someError{"unsupported protocol scheme: %s", URL.Scheme}
	}

	if !hasPort(URL.Host) {
		URL.Host += ":http"
	}

	return URL, nil
}

func send(url *http.URL, out chan result) {
	var req http.Request
	req.URL = url
	rerr := func (err os.Error) result { return result{0, err} }

	now := time.Nanoseconds()
	conn, err := net.Dial("tcp", "", req.URL.Host)
	if err != nil {
		out <- rerr(err)
		return
	}

	err = req.Write(conn)
	if err != nil {
		conn.Close()
		out <- rerr(err)
		return
	}

	reader := bufio.NewReader(conn)
	resp, err := http.ReadResponse(reader, req.Method)
	out <- result{time.Nanoseconds() - now, err}

	conn.Close()
	resp.Body.Close()
}
<commit_msg>some progress display<commit_after>package main

import (
	"flag"
	"fmt"
	"http"
	"time"
	"strings"
	"os"
	"net"
	"bufio"
	"sort"
	)

var reqs *int = flag.Int("n", 1, "number of requests to make")
var concurrency *int = flag.Int("c", 1, "concurrency")

type someError struct {
	what string
	str  string
}

func (e *someError) String() string {
	return fmt.Sprintf("%s %q", e.what, e.str)
}

type result struct {
	time int64
	err os.Error
}

type Int64Array []int64
func (p Int64Array) Len() int           { return len(p) }
func (p Int64Array) Less(i, j int) bool { return p[i] < p[j] }
func (p Int64Array) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func main() {
	flag.Parse()

	if len(flag.Args()) == 0 {
		flag.Usage()
		return
	}

	url, err := getURL(flag.Arg(0))
	if err != nil {
		fmt.Printf("url is invalid: %s", err)
		return
	}

	fmt.Printf("Statistics for requests to %s\n", flag.Arg(0))

	ch := make(chan result, *reqs)
	results := make([]result, *reqs)
	running, i, j := 0, 0, 0
	hadProgress := false

	now := time.Nanoseconds()
	for {
		if running < *concurrency && i < *reqs {
			go send(url, ch)
			running++
			i++
		} else if j < *reqs {
			results[j] = <- ch
			j++
			running--

			if j > 0 && j % 100 == 0 {
				fmt.Printf("\rCompleted %d from %d requests", j, *reqs)
				hadProgress = true
			}
		}

		if i == j && j >= *reqs {
			break
		}
	}

	if hadProgress {
		fmt.Print("\r                                  ")
	}

	printStats(results, time.Nanoseconds() - now)
}

func printStats(results []result, workTime int64) {
	times := make(Int64Array, 0)
	total := int64(0)

	for _, r := range(results) {
		if r.err == nil {
			times = append(times, r.time)
			total += r.time
		}
	}
	sort.Sort(times)

	fmt.Printf(`
Total requests performed:       %d
Total failures:                 %d
Time taken for tests:           %.3f ms
Average request takes:          %.3f ms
Median request time:            %.3f ms
Average time between responses: %.3f ms
`,
		*reqs,
		*reqs - len(times),
		ms(workTime),
		ms(total / int64(len(times))),
		ms(times[len(times) / 2]),
		ms(workTime / int64(*reqs)))
}

func ms(x int64) (float64) {
	return float64(x) / 1000000
}

func hasPort(s string) bool {
	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}

func getURL(url string) (*http.URL, os.Error) {
	URL, err := http.ParseURL(url)
	if err != nil {
		return nil, err
	}

	if URL.Scheme != "http" {
		return nil, &someError{"unsupported protocol scheme: %s", URL.Scheme}
	}

	if !hasPort(URL.Host) {
		URL.Host += ":http"
	}

	return URL, nil
}

func send(url *http.URL, out chan result) {
	var req http.Request
	req.URL = url
	rerr := func (err os.Error) result { return result{0, err} }

	now := time.Nanoseconds()
	conn, err := net.Dial("tcp", "", req.URL.Host)
	if err != nil {
		out <- rerr(err)
		return
	}

	err = req.Write(conn)
	if err != nil {
		conn.Close()
		out <- rerr(err)
		return
	}

	reader := bufio.NewReader(conn)
	resp, err := http.ReadResponse(reader, req.Method)
	out <- result{time.Nanoseconds() - now, err}

	conn.Close()
	resp.Body.Close()
}
<|endoftext|>


<commit_before>package gotumblr

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

//defines a Go Client for the Tumblr API
type TumblrRestClient struct {
	request *TumblrRequest
}

//Initializes the TumblrRestClient, creating TumblrRequest that deals with all request formatting.
//consumerKey is the consumer key of your Tumblr Application
//consumerSecret is the consumer secret of your Tumblr Application
//oauthToken is the user specific token, received from the /access_token endpoint
//oauthSecret is the user specific sechttps://www.facebook.com/?stype=lo&jlou=Afd0i67De7AJHB-KpdsH0H3JJVp662nkyJaGWT1_lAO-81gDDZkopEwv6h9VAazWbRuaaRKLB9GaTl7LataozuWw1bOBSpPo_gImejlKpAStTw&smuh=13421&lh=Ac9QPOHNfIdte3jLret, received from the /access_token endpoint
//host is the host that you are tryng to send information to (e.g. http://api.tumblr.com)
func NewTumblrRestClient(consumerKey, consumerSecret, oauthToken, oauthSecret, callbackUrl, host string) *TumblrRestClient {
	return &TumblrRestClient{NewTumblrRequest(consumerKey, consumerSecret, oauthToken, oauthSecret, callbackUrl, host)}
}

//Gets the user information
func (trc *TumblrRestClient) Info() map[string]interface{} {
	return trc.request.Get("/v2/user/info", map[string]string{})
}

//Retrieves the url of the blog's avatar
//size can be: 16, 24, 30, 40, 48, 64, 96, 128 or 512
func (trc *TumblrRestClient) Avatar(blogname string, size int) map[string]interface{} {
	requestUrl := fmt.Sprintf("/v2/blog/%s/avatar/%d", blogname, size)
	httpRequest, _ := http.NewRequest("GET", requestUrl, nil)
	var httpResponse *http.Response
	transport := &http.Transport{}
	httpResponse, _ = tr.RoundTrip(httpRequest)
	defer httpResponse.Body.Close()
	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		fmt.Println(err)
	}
	return trc.request.JsonParse(body)
}

//Gets the likes of the given user
//options can be:
//limit: the number of results to return, inclusive
//offset: liked post number to start at
func (trc *TumblrRestClient) Likes(options map[string]string) map[string]interface{} {
	return trc.request.Get("/v2/user/likes", options)
}

//Gets the blogs that the user is following.
//options can be:
//limit: the number of results to return
//offset: result number to start at
func (trc *TumblrRestClient) Following(options map[string]string) map[string]interface{} {
	return trc.request.Get("/v2/user/following", options)
}

//Gets the dashboard of the user
//options can be:
//limit: number of results to return
//offset: post number to start at
//type: the type of posts to return(text, photo, quote, link, chat, audio, video, answer)
//since_id: return posts that have apeared after this id
//reblog_info: whether to return reblog information about the posts
//notes_info: whether to return notes information about the posts
func (trc *TumblrRestClient) Dashboard(options map[string]string) map[string]interface{} {
	return trc.request.Get("/v2/user/dashboard", options)
}

//Gets a list of posts with the given tag
//tag: the tag you want to look for
//options can be:
//before: the timestamp of when you'd like to see posts before
//limit: the number of results to return
//filter: the post format you want to get(e.g html, text, raw)
func (trc *TumblrRestClient) Tagged(tag string, options map[string]string) map[string]interface{} {
	options["tag"] = tag
	return trc.request.Get("/v2/tagged", params)
}

//Gets a list of posts from a blog
//blogname: the name of the blog you want to get posts from (e.g. mariaterzieva.tumblr.com)
//postsType: the type of the posts you want to get
//(e.g. text, quote, link, answer, video, audio, photo, chat, all)
//options can be:
//id: the id of the post you are looking for
//tag: return only posts with this tag
//limit: the number of posts to return
//offset: the number of the post you want to start from
//filter: return only posts with a specific format(e.g. html, text, raw)
func (trc *TumblrRestClient) Posts(blogname, postsType string, options map[string]string) map[string]interface{} {
	if postsType == "" {
		requestUrl := fmt.Sprintf("/v2/blog/%s/posts", blogname)
	} else {
		requestUrl := fmt.Sprintf("/v2/blog/%s/posts/%s", blogname, postsType)
	}
	options["api_key"] = trc.request.apiKey
	return trc.request.Get(requestUrl, options)
}

//Gets general information about the blog
//blogname: name of the blog you want to get information about(e.g. mariaterzieva.tumblr.com)
func (trc *TumblrRestClient) BlogInfo(blogname string) map[string]interface{} {
	requestUrl := fmt.Sprintf("/v2/blog/%s/info", blogname)
	options := map[string]string{"api_key": trc.request.apiKey}
	trc.request.Get(requestUrl, options)
}

//Gets the followers of the blog given
//blogname: name of the blog whose followers you want to get
//optons can be:
//limit: the number of results to return, inclusive
//offset: result to start at
func (trc *TumblrRestClient) Followers(blogname string, options map[string]string) map[string]interface{} {

}

//Gets the likes of blog given
//blogname: name of the blog whose likes you want to get
//options can be:
//limit: how many likes do you want to get
//offset: the number of the like you want to start from
func (trc *TumblrRestClient) BlogLikes(blogname string, options map[string]string) map[string]interface{} {

}

//Gets posts that are currently in the blog's queue
//options can be:
//limit: the number of results to return
//offset: post number to start at
//filter: specify posts' format(e.g. format="html", format="text", format="raw")
func (trc *TumblrRestClient) Queue(blogname string, options map[string]string) map[string]interface{} {

}

//Gets posts that are currently in the blog's drafts
//options can be:
//filter: specify posts' format(e.g. format="html", format="text", format="raw")
func (trc *TumblrRestClient) Drafts(blogname string, options map[string]string) map[string]interface{} {

}

//Retrieve submission posts
//options can be:
//offset: post number to start at
//filter: specify posts' format(e.g. format="html", format="text", format="raw")
func (trc *TumblrRestClient) Submission(blogname string, options map[string]string) map[string]interface{} {

}

//Follow the url of a given blog
//blogname: the url of the blog to follow
func (trc *TumblrRestClient) Follow(blogname string) map[string]interface{} {

}

//Unfollow the url of a given blog
//blogname: the url of the blog to unfollow
func (trc *TumblrRestClient) Unfollow(blogname string) map[string]interface{} {

}

//Like post of a given blog
//id: the id of the post you want to like
//reblogKey: the reblog key for the post id
func (trc *TumblrRestClient) Like(id int, reblogKey string) map[string]interface{} {

}

//Unlike a post of a given blog
//id: the id of the post you want to unlike
//reblogKey: the reblog key for the post id
func (trc *TumblrRestClient) Unlike(id int, reblogKey string) map[string]interface{} {

}

//Create a photo post or photoset on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//caption: the caption that you want applied to the photo
//link: the 'click-through' url for the photo
//source: the photo source url(either source or data)
//data: one or more image files(wither source or data)
func (trc *TumblrRestClient) CreatePhoto(blogname string, options map[string]string) map[string]interface{} {

}

//Create a text post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//title: the optional title of the post
//body: the full text body
func (trc *TumblrRestClient) CreateText(blogname string, options map[string]string) map[string]interface{} {

}

//Create a quote post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//quote: the full text of the quote
//source: the cited source of the quote
func (trc *TumblrRestClient) CreateQuote(blogname string, options map[string]string) map[string]interface{} {

}

//Create a link post on a blog
//blogname: the url of the blog you want to po.tumblr.comst to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//title: the title of the page the link points to
//url: the link you are posting
//description: the description of the link you are posting
func (trc *TumblrRestClient) CreateLink(blogname string, options map[string]string) map[string]interface{} {

}

//Create a chat post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//title: the title of the chat
//conversation: the text of the conversation/chat, with dialogue labels
func (trc *TumblrRestClient) CreateChatPost(blogname string, options map[string]string) map[string]interface{} {

}

//Create an audio post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//caption: the caption of the post
//external_url: the url of the site that hosts the oudio file
//data: the local filename path to the audio you are uploading
func (trc *TumblrRestClient) CreateAudio(blogname string, options map[string]string) map[string]interface{} {

}

//Create a video post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//caption: the caption for the post
//embed: the html embed code for the video
//data: the local filename path to the video you are uploading
func (trc *TumblrRestClient) CreateVideo(blogname string, options map[string]string) map[string]interface{} {

}

//Creates a reblog on the given blog
//blogname: the url of the blog you want to reblog to
//id: the id of the reblogged post
//reblogKey: the reblog key for the post
func (trc *TumblrRestClient) Reblog(blogname string, options map[string]string) map[string]interface{} {

}

//Deletes a post with a given id
//blogname: the url of the blog you want to delete from
//id: the id of the post you want to delete
func (trc *TumblrRestClient) DeletePost(blogname string, id int) map[string]interface{} {

}

//Edits a post with a given id
//blogname: the url of the blog you want to post to
//options can be:
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
func (trc *TumblrRestClient) EditPost(blogname string, options map[string]string) map[string]interface{} {

}
<commit_msg>Fix typo<commit_after>package gotumblr

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

//defines a Go Client for the Tumblr API
type TumblrRestClient struct {
	request *TumblrRequest
}

//Initializes the TumblrRestClient, creating TumblrRequest that deals with all request formatting.
//consumerKey is the consumer key of your Tumblr Application
//consumerSecret is the consumer secret of your Tumblr Application
//oauthToken is the user specific token, received from the /access_token endpoint
//oauthSecret is the user specific secret, received from the /access_token endpoint
//host is the host that you are tryng to send information to (e.g. http://api.tumblr.com)
func NewTumblrRestClient(consumerKey, consumerSecret, oauthToken, oauthSecret, callbackUrl, host string) *TumblrRestClient {
	return &TumblrRestClient{NewTumblrRequest(consumerKey, consumerSecret, oauthToken, oauthSecret, callbackUrl, host)}
}

//Gets the user information
func (trc *TumblrRestClient) Info() map[string]interface{} {
	return trc.request.Get("/v2/user/info", map[string]string{})
}

//Retrieves the url of the blog's avatar
//size can be: 16, 24, 30, 40, 48, 64, 96, 128 or 512
func (trc *TumblrRestClient) Avatar(blogname string, size int) map[string]interface{} {
	requestUrl := fmt.Sprintf("/v2/blog/%s/avatar/%d", blogname, size)
	httpRequest, _ := http.NewRequest("GET", requestUrl, nil)
	var httpResponse *http.Response
	transport := &http.Transport{}
	httpResponse, _ = tr.RoundTrip(httpRequest)
	defer httpResponse.Body.Close()
	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		fmt.Println(err)
	}
	return trc.request.JsonParse(body)
}

//Gets the likes of the given user
//options can be:
//limit: the number of results to return, inclusive
//offset: liked post number to start at
func (trc *TumblrRestClient) Likes(options map[string]string) map[string]interface{} {
	return trc.request.Get("/v2/user/likes", options)
}

//Gets the blogs that the user is following.
//options can be:
//limit: the number of results to return
//offset: result number to start at
func (trc *TumblrRestClient) Following(options map[string]string) map[string]interface{} {
	return trc.request.Get("/v2/user/following", options)
}

//Gets the dashboard of the user
//options can be:
//limit: number of results to return
//offset: post number to start at
//type: the type of posts to return(text, photo, quote, link, chat, audio, video, answer)
//since_id: return posts that have apeared after this id
//reblog_info: whether to return reblog information about the posts
//notes_info: whether to return notes information about the posts
func (trc *TumblrRestClient) Dashboard(options map[string]string) map[string]interface{} {
	return trc.request.Get("/v2/user/dashboard", options)
}

//Gets a list of posts with the given tag
//tag: the tag you want to look for
//options can be:
//before: the timestamp of when you'd like to see posts before
//limit: the number of results to return
//filter: the post format you want to get(e.g html, text, raw)
func (trc *TumblrRestClient) Tagged(tag string, options map[string]string) map[string]interface{} {
	options["tag"] = tag
	return trc.request.Get("/v2/tagged", params)
}

//Gets a list of posts from a blog
//blogname: the name of the blog you want to get posts from (e.g. mariaterzieva.tumblr.com)
//postsType: the type of the posts you want to get
//(e.g. text, quote, link, answer, video, audio, photo, chat, all)
//options can be:
//id: the id of the post you are looking for
//tag: return only posts with this tag
//limit: the number of posts to return
//offset: the number of the post you want to start from
//filter: return only posts with a specific format(e.g. html, text, raw)
func (trc *TumblrRestClient) Posts(blogname, postsType string, options map[string]string) map[string]interface{} {
	if postsType == "" {
		requestUrl := fmt.Sprintf("/v2/blog/%s/posts", blogname)
	} else {
		requestUrl := fmt.Sprintf("/v2/blog/%s/posts/%s", blogname, postsType)
	}
	options["api_key"] = trc.request.apiKey
	return trc.request.Get(requestUrl, options)
}

//Gets general information about the blog
//blogname: name of the blog you want to get information about(e.g. mariaterzieva.tumblr.com)
func (trc *TumblrRestClient) BlogInfo(blogname string) map[string]interface{} {
	requestUrl := fmt.Sprintf("/v2/blog/%s/info", blogname)
	options := map[string]string{"api_key": trc.request.apiKey}
	trc.request.Get(requestUrl, options)
}

//Gets the followers of the blog given
//blogname: name of the blog whose followers you want to get
//optons can be:
//limit: the number of results to return, inclusive
//offset: result to start at
func (trc *TumblrRestClient) Followers(blogname string, options map[string]string) map[string]interface{} {

}

//Gets the likes of blog given
//blogname: name of the blog whose likes you want to get
//options can be:
//limit: how many likes do you want to get
//offset: the number of the like you want to start from
func (trc *TumblrRestClient) BlogLikes(blogname string, options map[string]string) map[string]interface{} {

}

//Gets posts that are currently in the blog's queue
//options can be:
//limit: the number of results to return
//offset: post number to start at
//filter: specify posts' format(e.g. format="html", format="text", format="raw")
func (trc *TumblrRestClient) Queue(blogname string, options map[string]string) map[string]interface{} {

}

//Gets posts that are currently in the blog's drafts
//options can be:
//filter: specify posts' format(e.g. format="html", format="text", format="raw")
func (trc *TumblrRestClient) Drafts(blogname string, options map[string]string) map[string]interface{} {

}

//Retrieve submission posts
//options can be:
//offset: post number to start at
//filter: specify posts' format(e.g. format="html", format="text", format="raw")
func (trc *TumblrRestClient) Submission(blogname string, options map[string]string) map[string]interface{} {

}

//Follow the url of a given blog
//blogname: the url of the blog to follow
func (trc *TumblrRestClient) Follow(blogname string) map[string]interface{} {

}

//Unfollow the url of a given blog
//blogname: the url of the blog to unfollow
func (trc *TumblrRestClient) Unfollow(blogname string) map[string]interface{} {

}

//Like post of a given blog
//id: the id of the post you want to like
//reblogKey: the reblog key for the post id
func (trc *TumblrRestClient) Like(id int, reblogKey string) map[string]interface{} {

}

//Unlike a post of a given blog
//id: the id of the post you want to unlike
//reblogKey: the reblog key for the post id
func (trc *TumblrRestClient) Unlike(id int, reblogKey string) map[string]interface{} {

}

//Create a photo post or photoset on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//caption: the caption that you want applied to the photo
//link: the 'click-through' url for the photo
//source: the photo source url(either source or data)
//data: one or more image files(wither source or data)
func (trc *TumblrRestClient) CreatePhoto(blogname string, options map[string]string) map[string]interface{} {

}

//Create a text post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//title: the optional title of the post
//body: the full text body
func (trc *TumblrRestClient) CreateText(blogname string, options map[string]string) map[string]interface{} {

}

//Create a quote post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//quote: the full text of the quote
//source: the cited source of the quote
func (trc *TumblrRestClient) CreateQuote(blogname string, options map[string]string) map[string]interface{} {

}

//Create a link post on a blog
//blogname: the url of the blog you want to po.tumblr.comst to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//title: the title of the page the link points to
//url: the link you are posting
//description: the description of the link you are posting
func (trc *TumblrRestClient) CreateLink(blogname string, options map[string]string) map[string]interface{} {

}

//Create a chat post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//title: the title of the chat
//conversation: the text of the conversation/chat, with dialogue labels
func (trc *TumblrRestClient) CreateChatPost(blogname string, options map[string]string) map[string]interface{} {

}

//Create an audio post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//caption: the caption of the post
//external_url: the url of the site that hosts the oudio file
//data: the local filename path to the audio you are uploading
func (trc *TumblrRestClient) CreateAudio(blogname string, options map[string]string) map[string]interface{} {

}

//Create a video post on a blog
//blogname: the url of the blog you want to post to
//options can be:
//state: the state of the post(e.g. published, draft, queue, private)
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
//caption: the caption for the post
//embed: the html embed code for the video
//data: the local filename path to the video you are uploading
func (trc *TumblrRestClient) CreateVideo(blogname string, options map[string]string) map[string]interface{} {

}

//Creates a reblog on the given blog
//blogname: the url of the blog you want to reblog to
//id: the id of the reblogged post
//reblogKey: the reblog key for the post
func (trc *TumblrRestClient) Reblog(blogname string, options map[string]string) map[string]interface{} {

}

//Deletes a post with a given id
//blogname: the url of the blog you want to delete from
//id: the id of the post you want to delete
func (trc *TumblrRestClient) DeletePost(blogname string, id int) map[string]interface{} {

}

//Edits a post with a given id
//blogname: the url of the blog you want to post to
//options can be:
//tags: a list of tags you want applied to the post
//tweet: manages the autotweet for this post: set to off for no tweet
//		 or enter text to override the default tweet
//date: the GMT date and time of the post as a string
//format: sets the format type of the post(html or markdown)
//slug: add a short text summary to the end of the post url
func (trc *TumblrRestClient) EditPost(blogname string, options map[string]string) map[string]interface{} {

}
<|endoftext|>


<commit_before>package restpc

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type Request interface {
	RemoteIP() (string, error)
	URL() *url.URL
	Host() string

	Body() ([]byte, error)
	BodyMap() (map[string]interface{}, error)
	BodyTo(model interface{}) error

	GetHeader(string) string

	GetString(key string, flags ...ParamFlag) (*string, error)
	GetStringList(key string, flags ...ParamFlag) ([]string, error)
	GetInt(key string, flags ...ParamFlag) (*int, error)
	GetFloat(key string, flags ...ParamFlag) (*float64, error)
	GetBool(key string, flags ...ParamFlag) (*bool, error)
	GetTime(key string, flags ...ParamFlag) (*time.Time, error)

	FullMap() map[string]interface{}
}

type requestImp struct {
	r          *http.Request
	body       []byte
	bodyErr    error
	bodyMap    map[string]interface{}
	bodyMapErr error
}

func (req *requestImp) RemoteIP() (string, error) {
	remoteIp, _, err := net.SplitHostPort(req.r.RemoteAddr)
	if err != nil {
		return "", NewError(
			Internal, "", err,
			"r.RemoteAddr", req.r.RemoteAddr,
		)
	}
	return remoteIp, nil
}

func (req *requestImp) URL() *url.URL {
	return req.r.URL
}

func (req *requestImp) Host() string {
	return req.r.Host
}

func (req *requestImp) Body() ([]byte, error) {
	if req.body != nil {
		return req.body, nil
	}
	if req.bodyErr != nil {
		return nil, req.bodyErr
	}
	if req.r.Body == nil {
		return nil, nil
	}
	body, err := ioutil.ReadAll(req.r.Body)
	if err != nil {
		req.bodyErr = err
		log.Println(err)
	}
	req.body = body
	req.r.Body.Close()
	req.r.Body = nil
	return body, nil
}

func (req *requestImp) BodyMap() (map[string]interface{}, error) {
	if req.bodyMap != nil {
		return req.bodyMap, nil
	}
	if req.bodyMapErr != nil {
		return nil, req.bodyMapErr
	}
	data := map[string]interface{}{}
	body, err := req.Body()
	if err != nil {
		req.bodyMapErr = err
		return nil, err
	}
	if len(body) > 0 {
		err = json.Unmarshal(body, &data)
		if err != nil {
			req.bodyMapErr = err
			log.Println(err)
			// return nil, err // FIXME
		}
	}
	req.bodyMap = data
	return data, nil
}

func (req *requestImp) BodyTo(model interface{}) error {
	body, err := req.Body()
	if err != nil {
		return err
	}
	err = json.Unmarshal(body, model)
	if err != nil {
		return NewError(InvalidArgument, "request body is not a valid json", err)
	}
	return nil
}

func (req *requestImp) GetHeader(key string) string {
	return req.r.Header.Get(key)
}

func (req *requestImp) GetString(key string, flags ...ParamFlag) (*string, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case string:
				valueStr := value // to copy
				return &valueStr, nil
			case []byte:
				valueStr := string(value)
				return &valueStr, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be string", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		value := req.r.FormValue(key)
		if value != "" {
			return &value, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetStringList(key string, flags ...ParamFlag) ([]string, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case []string:
				valueSlice := append([]string(nil), value...) // to copy
				return valueSlice, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be array of strings", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			valueSlice := strings.Split(valueStr, ",")
			return valueSlice, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetInt(key string, flags ...ParamFlag) (*int, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case float64:
				valueInt := int(value)
				return &valueInt, nil
			case int:
				valueInt := value // to copy
				return &valueInt, nil
			case int32:
				valueInt := int(value)
				return &valueInt, nil
			case int64:
				valueInt := int(value)
				return &valueInt, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be integer", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			value, err := strconv.ParseInt(valueStr, 10, 64)
			if err != nil {
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be integer", key),
					err,
					"valueStr", valueStr,
				)
			}
			valueInt := int(value)
			return &valueInt, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetFloat(key string, flags ...ParamFlag) (*float64, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case float64:
				valueF := value // to copy
				return &valueF, nil
			case float32:
				valueF := float64(value)
				return &valueF, nil
			case int:
				valueF := float64(value)
				return &valueF, nil
			case int64:
				valueF := float64(value)
				return &valueF, nil
			case int32:
				valueF := float64(value)
				return &valueF, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be float", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			value, err := strconv.ParseFloat(valueStr, 64)
			if err != nil {
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be float", key),
					err,
					"valueStr", valueStr,
				)
			}
			valueF := float64(value)
			return &valueF, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetBool(key string, flags ...ParamFlag) (*bool, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case bool:
				valueBool := value // to copy
				return &valueBool, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be true or false", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			valueStr = strings.ToLower(valueStr)
			switch valueStr {
			case "true":
				valueBool := true
				return &valueBool, nil
			case "false":
				valueBool := false
				return &valueBool, nil
			}
			return nil, NewError(
				InvalidArgument,
				fmt.Sprintf("invalid '%v', must be true or false", key),
				nil,
				"valueStr", valueStr,
			)
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetTime(key string, flags ...ParamFlag) (*time.Time, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case string:
				valueTm, err := time.Parse(time.RFC3339, value)
				if err != nil {
					return nil, NewError(
						InvalidArgument,
						fmt.Sprintf("invalid '%v', must be RFC3339 time string", key),
						err,
						"value", value,
					)
				}
				return &valueTm, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be RFC3339 time string", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			valueTm, err := time.Parse(time.RFC3339, valueStr)
			if err != nil {
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be RFC3339 time string", key),
					err,
					"valueStr", valueStr,
				)
			}
			return &valueTm, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) HeaderCopy() http.Header {
	header := http.Header{}
	for key, values := range req.r.Header {
		header[key] = values
	}
	return header
}

func (req *requestImp) HeaderStrippedAuth() http.Header {
	header := req.HeaderCopy()
	authHader, ok := header["Authorization"]
	if ok {
		authHaderNew := make([]string, len(authHader))
		for i := 0; i < len(authHader); i++ {
			authHaderNew[i] = "[REMOVED]"
		}
		header["Authorization"] = authHaderNew
	}
	return header
}

func (req *requestImp) FullMap() map[string]interface{} {
	bodyMap, _ := req.BodyMap()
	urlStr := req.URL().String()
	remoteIP, _ := req.RemoteIP()
	return map[string]interface{}{
		"bodyMap":  bodyMap,
		"url":      urlStr,
		"form":     req.r.Form,
		"header":   req.HeaderStrippedAuth(),
		"remoteIP": remoteIP,
	}
}
<commit_msg>request.FullMap(): add "context"<commit_after>package restpc

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type Request interface {
	RemoteIP() (string, error)
	URL() *url.URL
	Host() string

	Body() ([]byte, error)
	BodyMap() (map[string]interface{}, error)
	BodyTo(model interface{}) error

	GetHeader(string) string

	GetString(key string, flags ...ParamFlag) (*string, error)
	GetStringList(key string, flags ...ParamFlag) ([]string, error)
	GetInt(key string, flags ...ParamFlag) (*int, error)
	GetFloat(key string, flags ...ParamFlag) (*float64, error)
	GetBool(key string, flags ...ParamFlag) (*bool, error)
	GetTime(key string, flags ...ParamFlag) (*time.Time, error)

	FullMap() map[string]interface{}
}

type requestImp struct {
	r          *http.Request
	body       []byte
	bodyErr    error
	bodyMap    map[string]interface{}
	bodyMapErr error
}

func (req *requestImp) RemoteIP() (string, error) {
	remoteIp, _, err := net.SplitHostPort(req.r.RemoteAddr)
	if err != nil {
		return "", NewError(
			Internal, "", err,
			"r.RemoteAddr", req.r.RemoteAddr,
		)
	}
	return remoteIp, nil
}

func (req *requestImp) URL() *url.URL {
	return req.r.URL
}

func (req *requestImp) Host() string {
	return req.r.Host
}

func (req *requestImp) Body() ([]byte, error) {
	if req.body != nil {
		return req.body, nil
	}
	if req.bodyErr != nil {
		return nil, req.bodyErr
	}
	if req.r.Body == nil {
		return nil, nil
	}
	body, err := ioutil.ReadAll(req.r.Body)
	if err != nil {
		req.bodyErr = err
		log.Println(err)
	}
	req.body = body
	req.r.Body.Close()
	req.r.Body = nil
	return body, nil
}

func (req *requestImp) BodyMap() (map[string]interface{}, error) {
	if req.bodyMap != nil {
		return req.bodyMap, nil
	}
	if req.bodyMapErr != nil {
		return nil, req.bodyMapErr
	}
	data := map[string]interface{}{}
	body, err := req.Body()
	if err != nil {
		req.bodyMapErr = err
		return nil, err
	}
	if len(body) > 0 {
		err = json.Unmarshal(body, &data)
		if err != nil {
			req.bodyMapErr = err
			log.Println(err)
			// return nil, err // FIXME
		}
	}
	req.bodyMap = data
	return data, nil
}

func (req *requestImp) BodyTo(model interface{}) error {
	body, err := req.Body()
	if err != nil {
		return err
	}
	err = json.Unmarshal(body, model)
	if err != nil {
		return NewError(InvalidArgument, "request body is not a valid json", err)
	}
	return nil
}

func (req *requestImp) GetHeader(key string) string {
	return req.r.Header.Get(key)
}

func (req *requestImp) GetString(key string, flags ...ParamFlag) (*string, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case string:
				valueStr := value // to copy
				return &valueStr, nil
			case []byte:
				valueStr := string(value)
				return &valueStr, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be string", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		value := req.r.FormValue(key)
		if value != "" {
			return &value, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetStringList(key string, flags ...ParamFlag) ([]string, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case []string:
				valueSlice := append([]string(nil), value...) // to copy
				return valueSlice, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be array of strings", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			valueSlice := strings.Split(valueStr, ",")
			return valueSlice, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetInt(key string, flags ...ParamFlag) (*int, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case float64:
				valueInt := int(value)
				return &valueInt, nil
			case int:
				valueInt := value // to copy
				return &valueInt, nil
			case int32:
				valueInt := int(value)
				return &valueInt, nil
			case int64:
				valueInt := int(value)
				return &valueInt, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be integer", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			value, err := strconv.ParseInt(valueStr, 10, 64)
			if err != nil {
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be integer", key),
					err,
					"valueStr", valueStr,
				)
			}
			valueInt := int(value)
			return &valueInt, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetFloat(key string, flags ...ParamFlag) (*float64, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case float64:
				valueF := value // to copy
				return &valueF, nil
			case float32:
				valueF := float64(value)
				return &valueF, nil
			case int:
				valueF := float64(value)
				return &valueF, nil
			case int64:
				valueF := float64(value)
				return &valueF, nil
			case int32:
				valueF := float64(value)
				return &valueF, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be float", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			value, err := strconv.ParseFloat(valueStr, 64)
			if err != nil {
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be float", key),
					err,
					"valueStr", valueStr,
				)
			}
			valueF := float64(value)
			return &valueF, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetBool(key string, flags ...ParamFlag) (*bool, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case bool:
				valueBool := value // to copy
				return &valueBool, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be true or false", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			valueStr = strings.ToLower(valueStr)
			switch valueStr {
			case "true":
				valueBool := true
				return &valueBool, nil
			case "false":
				valueBool := false
				return &valueBool, nil
			}
			return nil, NewError(
				InvalidArgument,
				fmt.Sprintf("invalid '%v', must be true or false", key),
				nil,
				"valueStr", valueStr,
			)
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) GetTime(key string, flags ...ParamFlag) (*time.Time, error) {
	flag := mergeParamFlags(flags...)
	if flag.FromBody() {
		data, err := req.BodyMap()
		if err != nil {
			return nil, err
		}
		valueIn := data[key]
		if valueIn != nil {
			switch value := valueIn.(type) {
			case string:
				valueTm, err := time.Parse(time.RFC3339, value)
				if err != nil {
					return nil, NewError(
						InvalidArgument,
						fmt.Sprintf("invalid '%v', must be RFC3339 time string", key),
						err,
						"value", value,
					)
				}
				return &valueTm, nil
			default:
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be RFC3339 time string", key),
					nil,
					"value", value,
				)
			}
		}
	}
	if flag.FromForm() {
		valueStr := req.r.FormValue(key)
		if valueStr != "" {
			valueTm, err := time.Parse(time.RFC3339, valueStr)
			if err != nil {
				return nil, NewError(
					InvalidArgument,
					fmt.Sprintf("invalid '%v', must be RFC3339 time string", key),
					err,
					"valueStr", valueStr,
				)
			}
			return &valueTm, nil
		}
	}
	if flag.Mandatory() {
		return nil, NewError(
			InvalidArgument,
			fmt.Sprintf("missing '%v'", key),
			nil,
		)
	}
	return nil, nil
}

func (req *requestImp) HeaderCopy() http.Header {
	header := http.Header{}
	for key, values := range req.r.Header {
		header[key] = values
	}
	return header
}

func (req *requestImp) HeaderStrippedAuth() http.Header {
	header := req.HeaderCopy()
	authHader, ok := header["Authorization"]
	if ok {
		authHaderNew := make([]string, len(authHader))
		for i := 0; i < len(authHader); i++ {
			authHaderNew[i] = "[REMOVED]"
		}
		header["Authorization"] = authHaderNew
	}
	return header
}

func (req *requestImp) FullMap() map[string]interface{} {
	bodyMap, _ := req.BodyMap()
	urlStr := req.URL().String()
	remoteIP, _ := req.RemoteIP()
	return map[string]interface{}{
		"bodyMap":  bodyMap,
		"url":      urlStr,
		"form":     req.r.Form,
		"header":   req.HeaderStrippedAuth(),
		"remoteIP": remoteIP,
		"context":  req.r.Context(),
	}
}
<|endoftext|>


<commit_before>package gadget

import (
	"encoding/json"
	"fmt"
	"github.com/redneckbeard/gadget/env"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

// Request wraps an *http.Request and adds some Gadget-derived conveniences. The
// Params map contains either POST data, GET query parameters, or the body of the
// request deserialized as JSON if the request sends an Accept header of
// application/json. The UrlParams map contains any resource ids plucked from the
// URL by the router. The User is either an AnonymousUser or an object returned by
// the UserIdentifier that the application as registered with IdentifyUsersWith.
type Request struct {
	*http.Request
	Path      string
	Params    map[string]interface{}
	Method    string
	UrlParams map[string]string
	User      User
}

func newRequest(raw *http.Request) *Request {
	r := &Request{Request: raw, Path: raw.URL.Path[1:], Method: raw.Method}
	r.setParams()
	return r
}

func (r *Request) ContentType() string {
	accept := r.Request.Header.Get("Accept")
	if accept != "" {
		return accept
	}
	return r.Request.Header.Get("Content-Type")
}

func unpackValues(params map[string]interface{}, values map[string][]string) {
	for k, v := range values {
		if len(v) == 1 {
			params[k] = v[0]
		} else {
			params[k] = v
		}
	}
}

func (r *Request) setParams() {
	params := make(map[string]interface{})
	switch ct := r.Request.Header.Get("Content-Type"); {
	case ct == "application/json":
		if r.Request.Body != nil {
			raw, err := ioutil.ReadAll(r.Request.Body)
			defer r.Request.Body.Close()
			if err != nil {
				return
			}
			err = json.Unmarshal(raw, &params)
			if err != nil {
				env.Log("Unable to deserialize JSON payload: ", err, string(raw))
				return
			}
		}
	case strings.HasPrefix(ct, "multipart/form-data"):
		err := r.ParseMultipartForm(10 * 1024 * 1024)
		if err != nil {
			return
		}
		unpackValues(params, r.MultipartForm.Value)
	default:
		err := r.ParseForm()
		if err != nil {
			return
		}
		unpackValues(params, r.Form)
	}
	r.Params = params
}

func (r *Request) setUser() error {
	if identifyUser != nil {
		r.User = identifyUser(r)
	} else {
		r.User = &AnonymousUser{}
	}
	return nil
}

func (r *Request) log(status, contentLength int) {
	raw := r.Request
	env.Log(fmt.Sprintf(`[%s] "%s %s %s" %d %d`, time.Now().Format(time.RFC822), r.Method, raw.URL.Path, raw.Proto, status, contentLength))
}
<commit_msg>Allow client applications to define per-request debug status.<commit_after>package gadget

import (
	"encoding/json"
	"fmt"
	"github.com/redneckbeard/gadget/env"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

type DebugChecker func(*Request) bool

var SetDebugWith DebugChecker = func(r *Request) bool { return false }

// Request wraps an *http.Request and adds some Gadget-derived conveniences. The
// Params map contains either POST data, GET query parameters, or the body of the
// request deserialized as JSON if the request sends an Accept header of
// application/json. The UrlParams map contains any resource ids plucked from the
// URL by the router. The User is either an AnonymousUser or an object returned by
// the UserIdentifier that the application as registered with IdentifyUsersWith.
type Request struct {
	*http.Request
	Path      string
	Params    map[string]interface{}
	Method    string
	UrlParams map[string]string
	User      User
}

func newRequest(raw *http.Request) *Request {
	r := &Request{Request: raw, Path: raw.URL.Path[1:], Method: raw.Method}
	r.setParams()
	return r
}

func (r *Request) ContentType() string {
	accept := r.Request.Header.Get("Accept")
	if accept != "" {
		return accept
	}
	return r.Request.Header.Get("Content-Type")
}

func (r *Request) Debug() bool {
	return env.Debug || SetDebugWith(r)
}

func unpackValues(params map[string]interface{}, values map[string][]string) {
	for k, v := range values {
		if len(v) == 1 {
			params[k] = v[0]
		} else {
			params[k] = v
		}
	}
}

func (r *Request) setParams() {
	params := make(map[string]interface{})
	switch ct := r.Request.Header.Get("Content-Type"); {
	case ct == "application/json":
		if r.Request.Body != nil {
			raw, err := ioutil.ReadAll(r.Request.Body)
			defer r.Request.Body.Close()
			if err != nil {
				return
			}
			err = json.Unmarshal(raw, &params)
			if err != nil {
				env.Log("Unable to deserialize JSON payload: ", err, string(raw))
				return
			}
		}
	case strings.HasPrefix(ct, "multipart/form-data"):
		err := r.ParseMultipartForm(10 * 1024 * 1024)
		if err != nil {
			return
		}
		unpackValues(params, r.MultipartForm.Value)
	default:
		err := r.ParseForm()
		if err != nil {
			return
		}
		unpackValues(params, r.Form)
	}
	r.Params = params
}

func (r *Request) setUser() error {
	if identifyUser != nil {
		r.User = identifyUser(r)
	} else {
		r.User = &AnonymousUser{}
	}
	return nil
}

func (r *Request) log(status, contentLength int) {
	raw := r.Request
	env.Log(fmt.Sprintf(`[%s] "%s %s %s" %d %d`, time.Now().Format(time.RFC822), r.Method, raw.URL.Path, raw.Proto, status, contentLength))
}
<|endoftext|>


<commit_before>package request

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
)

type RequestMethod string

const (
	GET     RequestMethod = "GET"
	HEAD    RequestMethod = "HEAD"
	PUT     RequestMethod = "PUT"
	POST    RequestMethod = "POST"
	OPTIONS RequestMethod = "OPTIONS"
	DELETE  RequestMethod = "DELETE"
)

type Request struct {
	url     string
	headers map[string]string
	body    *bytes.Buffer
	method  RequestMethod
}

func New(url string) *Request {

	r := &Request{
		url:    url,
		method: GET,
	}

	r.headers = make(map[string]string)

	return r
}

func (r *Request) Method(method RequestMethod) *Request {

	r.method = method

	return r
}

func Get(url string) *Request {

	return &Request{
		url:    url,
		method: GET,
	}
}

func Put(url string, body interface{}) *Request {

	requestBody, _ := json.Marshal(body)

	return &Request{
		url:    url,
		method: PUT,
		body:   bytes.NewBuffer(requestBody),
	}
}

func Head(url string) *Request {

	return &Request{
		url:    url,
		method: HEAD,
	}
}

func Options(url string) *Request {

	return &Request{
		url:    url,
		method: OPTIONS,
	}
}

func Post(url string, body interface{}) *Request {

	requestBody, _ := json.Marshal(body)

	return &Request{
		url:    url,
		method: POST,
		body:   bytes.NewBuffer(requestBody),
	}
}

func (r *Request) Header(key string, value string) *Request {

	r.headers[key] = value

	return r
}

func (r *Request) Headers(h map[string]string) *Request {

	for k, v := range h {
		r = r.Header(k, v)
	}

	return r
}

func (r *Request) Do() ([]byte, error) {

	if r.method == "" {
		r.method = GET
	}

	cli := &http.Client{}
	req, err := http.NewRequest(r.method.string(), r.url, nil)
	if err != nil {
		return nil, err
	}

	for header, value := range r.headers {
		req.Header.Add(header, value)
	}

	res, err := cli.Do(req)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

func (r *Request) Map(i interface{}) error {

	resp, err := r.Do()
	if err != nil {
		return err
	}

	err = json.Unmarshal(resp, &i)

	return err
}

func (m RequestMethod) string() string {
	switch m {
	case HEAD:
		return "HEAD"
	case PUT:
		return "PUT"
	case POST:
		return "POST"
	case OPTIONS:
		return "OPTIONS"
	}

	return "GET"
}
<commit_msg>add request body<commit_after>package request

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
)

type RequestMethod string

const (
	GET     RequestMethod = "GET"
	HEAD    RequestMethod = "HEAD"
	PUT     RequestMethod = "PUT"
	POST    RequestMethod = "POST"
	OPTIONS RequestMethod = "OPTIONS"
	DELETE  RequestMethod = "DELETE"
)

type Request struct {
	url     string
	headers map[string]string
	body    *bytes.Buffer
	method  RequestMethod
}

func New(url string) *Request {

	r := &Request{
		url:    url,
		method: GET,
	}

	r.headers = make(map[string]string)

	return r
}

func (r *Request) Method(method RequestMethod) *Request {

	r.method = method

	return r
}

func Get(url string) *Request {

	return &Request{
		url:    url,
		method: GET,
	}
}

func Put(url string, body interface{}) *Request {

	requestBody, _ := json.Marshal(body)

	return &Request{
		url:    url,
		method: PUT,
		body:   bytes.NewBuffer(requestBody),
	}
}

func Head(url string) *Request {

	return &Request{
		url:    url,
		method: HEAD,
	}
}

func Options(url string) *Request {

	return &Request{
		url:    url,
		method: OPTIONS,
	}
}

func Post(url string, body interface{}) *Request {

	requestBody, _ := json.Marshal(body)

	return &Request{
		url:    url,
		method: POST,
		body:   bytes.NewBuffer(requestBody),
	}
}

func (r *Request) Header(key string, value string) *Request {

	r.headers[key] = value

	return r
}

func (r *Request) Headers(h map[string]string) *Request {

	for k, v := range h {
		r = r.Header(k, v)
	}

	return r
}

func (r *Request) Do() ([]byte, error) {

	if r.method == "" {
		r.method = GET
	}

	cli := &http.Client{}
	req, err := http.NewRequest(r.method.string(), r.url, r.body)
	if err != nil {
		return nil, err
	}

	for header, value := range r.headers {
		req.Header.Add(header, value)
	}

	res, err := cli.Do(req)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

func (r *Request) Map(i interface{}) error {

	resp, err := r.Do()
	if err != nil {
		return err
	}

	err = json.Unmarshal(resp, &i)

	return err
}

func (m RequestMethod) string() string {
	switch m {
	case HEAD:
		return "HEAD"
	case PUT:
		return "PUT"
	case POST:
		return "POST"
	case OPTIONS:
		return "OPTIONS"
	}

	return "GET"
}
<|endoftext|>