<commit_before>package uploader

import (
	"encoding/json"
	"io/ioutil"
	"mime/multipart"
	"net/http"
)

type Handler struct {
	uploader *Uploader
}

func NewHandler(uploader *Uploader) *Handler {
	return &Handler{uploader: uploader}
}

func (h *Handler) HandleGet(res http.ResponseWriter, filename string) {
	if filename == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}
	imageData, err := h.uploader.Get(filename)
	if err != nil {
		if !h.uploader.Exists(filename) {
			res.WriteHeader(http.StatusNotFound)
			return
		}
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	res.WriteHeader(http.StatusOK)
	res.Write(imageData)
}

func (h *Handler) HandlePut(res http.ResponseWriter, file multipart.File, filename string) {
	if filename == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}
	imageData, err := ioutil.ReadAll(file)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	var status int

	if h.uploader.Exists(filename) {
		status = http.StatusOK
	} else {
		status = http.StatusCreated
	}

	err = h.uploader.Put(filename, imageData)

	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	res.WriteHeader(status)
	result, _ := json.Marshal(
		map[string]interface{}{
			"status":   "OK",
			"filename": filename,
		})

	res.Write(result)
}

func (h *Handler) HandlePost(res http.ResponseWriter, file multipart.File) {
	imageData, err := ioutil.ReadAll(file)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	filename, err := h.uploader.Store(imageData)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	res.WriteHeader(http.StatusCreated)
	result, _ := json.Marshal(
		map[string]interface{}{
			"status":   "OK",
			"filename": filename,
		})

	res.Write(result)
}

func (h *Handler) HandleDelete(res http.ResponseWriter, filename string) {
	if filename == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}
	if !h.uploader.Exists(filename) {
		res.WriteHeader(http.StatusNotFound)
		return
	}
	err := h.uploader.Delete(filename)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	res.WriteHeader(http.StatusOK)
	result, _ := json.Marshal(
		map[string]interface{}{
			"status": "OK",
		})

	res.Write(result)
}

func UploadHandler(uploader *Uploader, uploadField, filenameField string) http.HandlerFunc {
	handler := NewHandler(uploader)
	return func(res http.ResponseWriter, req *http.Request) {
		req.ParseForm()

		switch req.Method {
		case "GET":
			handler.HandleGet(res, req.FormValue(filenameField))
		case "PUT":
			file, _, err := req.FormFile(uploadField)
			if err != nil {
				res.WriteHeader(http.StatusBadRequest)
				return
			}
			handler.HandlePut(res, file, req.FormValue(filenameField))
		case "POST":
			file, _, err := req.FormFile(uploadField)
			if err != nil {
				res.WriteHeader(http.StatusBadRequest)
				return
			}
			handler.HandlePost(res, file)
		case "DELETE":
			handler.HandleDelete(res, req.FormValue(filenameField))
		default:
			res.WriteHeader(http.StatusMethodNotAllowed)
		}

	}
}
<commit_msg>Use defined status string<commit_after>package uploader

import (
	"encoding/json"
	"io/ioutil"
	"mime/multipart"
	"net/http"
)

const (
	ResponseStatusOK    = "OK"
	ResponseStatusError = "ERROR"
)

type Handler struct {
	uploader *Uploader
}

func NewHandler(uploader *Uploader) *Handler {
	return &Handler{uploader: uploader}
}

func (h *Handler) HandleGet(res http.ResponseWriter, filename string) {
	if filename == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}
	imageData, err := h.uploader.Get(filename)
	if err != nil {
		if !h.uploader.Exists(filename) {
			res.WriteHeader(http.StatusNotFound)
			return
		}
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	res.WriteHeader(http.StatusOK)
	res.Write(imageData)
}

func (h *Handler) HandlePut(res http.ResponseWriter, file multipart.File, filename string) {
	if filename == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}
	imageData, err := ioutil.ReadAll(file)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	var status int

	if h.uploader.Exists(filename) {
		status = http.StatusOK
	} else {
		status = http.StatusCreated
	}

	err = h.uploader.Put(filename, imageData)

	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	res.WriteHeader(status)
	result, _ := json.Marshal(
		map[string]interface{}{
			"status":   ResponseStatusOK,
			"filename": filename,
		})

	res.Write(result)
}

func (h *Handler) HandlePost(res http.ResponseWriter, file multipart.File) {
	imageData, err := ioutil.ReadAll(file)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}

	filename, err := h.uploader.Store(imageData)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	res.WriteHeader(http.StatusCreated)
	result, _ := json.Marshal(
		map[string]interface{}{
			"status":   ResponseStatusOK,
			"filename": filename,
		})

	res.Write(result)
}

func (h *Handler) HandleDelete(res http.ResponseWriter, filename string) {
	if filename == "" {
		res.WriteHeader(http.StatusBadRequest)
		return
	}
	if !h.uploader.Exists(filename) {
		res.WriteHeader(http.StatusNotFound)
		return
	}
	err := h.uploader.Delete(filename)
	if err != nil {
		res.WriteHeader(http.StatusInternalServerError)
		return
	}
	res.WriteHeader(http.StatusOK)
	result, _ := json.Marshal(
		map[string]interface{}{
			"status": ResponseStatusOK,
		})

	res.Write(result)
}

func UploadHandler(uploader *Uploader, uploadField, filenameField string) http.HandlerFunc {
	handler := NewHandler(uploader)
	return func(res http.ResponseWriter, req *http.Request) {
		req.ParseForm()

		switch req.Method {
		case "GET":
			handler.HandleGet(res, req.FormValue(filenameField))
		case "PUT":
			file, _, err := req.FormFile(uploadField)
			if err != nil {
				res.WriteHeader(http.StatusBadRequest)
				return
			}
			handler.HandlePut(res, file, req.FormValue(filenameField))
		case "POST":
			file, _, err := req.FormFile(uploadField)
			if err != nil {
				res.WriteHeader(http.StatusBadRequest)
				return
			}
			handler.HandlePost(res, file)
		case "DELETE":
			handler.HandleDelete(res, req.FormValue(filenameField))
		default:
			res.WriteHeader(http.StatusMethodNotAllowed)
		}

	}
}
<|endoftext|>


<commit_before>package handler

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/graphql-go/graphql"

	"context"
)

const (
	ContentTypeJSON           = "application/json"
	ContentTypeGraphQL        = "application/graphql"
	ContentTypeFormURLEncoded = "application/x-www-form-urlencoded"
)

type Handler struct {
	Schema *graphql.Schema
	pretty   bool
	graphiql bool
}
type RequestOptions struct {
	Query         string                 `json:"query" url:"query" schema:"query"`
	Variables     map[string]interface{} `json:"variables" url:"variables" schema:"variables"`
	OperationName string                 `json:"operationName" url:"operationName" schema:"operationName"`
}

// a workaround for getting`variables` as a JSON string
type requestOptionsCompatibility struct {
	Query         string `json:"query" url:"query" schema:"query"`
	Variables     string `json:"variables" url:"variables" schema:"variables"`
	OperationName string `json:"operationName" url:"operationName" schema:"operationName"`
}

func getFromForm(values url.Values) *RequestOptions {
	query := values.Get("query")
	if query != "" {
		// get variables map
		variables := make(map[string]interface{})
		variablesStr := values.Get("variables")
		json.Unmarshal([]byte(variablesStr), &variables)

		return &RequestOptions{
			Query:         query,
			Variables:     variables,
			OperationName: values.Get("operationName"),
		}
	}

	return nil
}

// RequestOptions Parses a http.Request into GraphQL request options struct
func NewRequestOptions(r *http.Request) *RequestOptions {
	if reqOpt := getFromForm(r.URL.Query()); reqOpt != nil {
		return reqOpt
	}

	if r.Method != "POST" {
		return &RequestOptions{}
	}

	if r.Body == nil {
		return &RequestOptions{}
	}

	// TODO: improve Content-Type handling
	contentTypeStr := r.Header.Get("Content-Type")
	contentTypeTokens := strings.Split(contentTypeStr, ";")
	contentType := contentTypeTokens[0]

	switch contentType {
	case ContentTypeGraphQL:
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return &RequestOptions{}
		}
		return &RequestOptions{
			Query: string(body),
		}
	case ContentTypeFormURLEncoded:
		if err := r.ParseForm(); err != nil {
			return &RequestOptions{}
		}

		if reqOpt := getFromForm(r.PostForm); reqOpt != nil {
			return reqOpt
		}

		return &RequestOptions{}

	case ContentTypeJSON:
		fallthrough
	default:
		var opts RequestOptions
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return &opts
		}
		err = json.Unmarshal(body, &opts)
		if err != nil {
			// Probably `variables` was sent as a string instead of an object.
			// So, we try to be polite and try to parse that as a JSON string
			var optsCompatible requestOptionsCompatibility
			json.Unmarshal(body, &optsCompatible)
			json.Unmarshal([]byte(optsCompatible.Variables), &opts.Variables)
		}
		return &opts
	}
}

// ContextHandler provides an entrypoint into executing graphQL queries with a
// user-provided context.
func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	// get query
	opts := NewRequestOptions(r)

	// execute graphql query
	params := graphql.Params{
		Schema:         *h.Schema,
		RequestString:  opts.Query,
		VariableValues: opts.Variables,
		OperationName:  opts.OperationName,
		Context:        ctx,
	}
	result := graphql.Do(params)

	if h.graphiql {
		acceptHeader := r.Header.Get("Accept")
		_, raw := r.URL.Query()["raw"]
		if !raw && !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") {
			renderGraphiQL(w, params)
			return
		}
	}

	// use proper JSON Header
	w.Header().Add("Content-Type", "application/json; charset=utf-8")

	if h.pretty {
		w.WriteHeader(http.StatusOK)
		buff, _ := json.MarshalIndent(result, "", "\t")

		w.Write(buff)
	} else {
		w.WriteHeader(http.StatusOK)
		buff, _ := json.Marshal(result)

		w.Write(buff)
	}
}

// ServeHTTP provides an entrypoint into executing graphQL queries.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.ContextHandler(r.Context(), w, r)
}

type Config struct {
	Schema   *graphql.Schema
	Pretty   bool
	GraphiQL bool
}

func NewConfig() *Config {
	return &Config{
		Schema:   nil,
		Pretty:   true,
		GraphiQL: true,
	}
}

func New(p *Config) *Handler {
	if p == nil {
		p = NewConfig()
	}
	if p.Schema == nil {
		panic("undefined GraphQL schema")
	}

	return &Handler{
		Schema:   p.Schema,
		pretty:   p.Pretty,
		graphiql: p.GraphiQL,
	}
}
<commit_msg>handler: use values length to preallocate<commit_after>package handler

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/graphql-go/graphql"

	"context"
)

const (
	ContentTypeJSON           = "application/json"
	ContentTypeGraphQL        = "application/graphql"
	ContentTypeFormURLEncoded = "application/x-www-form-urlencoded"
)

type Handler struct {
	Schema *graphql.Schema
	pretty   bool
	graphiql bool
}
type RequestOptions struct {
	Query         string                 `json:"query" url:"query" schema:"query"`
	Variables     map[string]interface{} `json:"variables" url:"variables" schema:"variables"`
	OperationName string                 `json:"operationName" url:"operationName" schema:"operationName"`
}

// a workaround for getting`variables` as a JSON string
type requestOptionsCompatibility struct {
	Query         string `json:"query" url:"query" schema:"query"`
	Variables     string `json:"variables" url:"variables" schema:"variables"`
	OperationName string `json:"operationName" url:"operationName" schema:"operationName"`
}

func getFromForm(values url.Values) *RequestOptions {
	query := values.Get("query")
	if query != "" {
		// get variables map
		variables := make(map[string]interface{}, len(values))
		variablesStr := values.Get("variables")
		json.Unmarshal([]byte(variablesStr), &variables)

		return &RequestOptions{
			Query:         query,
			Variables:     variables,
			OperationName: values.Get("operationName"),
		}
	}

	return nil
}

// RequestOptions Parses a http.Request into GraphQL request options struct
func NewRequestOptions(r *http.Request) *RequestOptions {
	if reqOpt := getFromForm(r.URL.Query()); reqOpt != nil {
		return reqOpt
	}

	if r.Method != "POST" {
		return &RequestOptions{}
	}

	if r.Body == nil {
		return &RequestOptions{}
	}

	// TODO: improve Content-Type handling
	contentTypeStr := r.Header.Get("Content-Type")
	contentTypeTokens := strings.Split(contentTypeStr, ";")
	contentType := contentTypeTokens[0]

	switch contentType {
	case ContentTypeGraphQL:
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return &RequestOptions{}
		}
		return &RequestOptions{
			Query: string(body),
		}
	case ContentTypeFormURLEncoded:
		if err := r.ParseForm(); err != nil {
			return &RequestOptions{}
		}

		if reqOpt := getFromForm(r.PostForm); reqOpt != nil {
			return reqOpt
		}

		return &RequestOptions{}

	case ContentTypeJSON:
		fallthrough
	default:
		var opts RequestOptions
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return &opts
		}
		err = json.Unmarshal(body, &opts)
		if err != nil {
			// Probably `variables` was sent as a string instead of an object.
			// So, we try to be polite and try to parse that as a JSON string
			var optsCompatible requestOptionsCompatibility
			json.Unmarshal(body, &optsCompatible)
			json.Unmarshal([]byte(optsCompatible.Variables), &opts.Variables)
		}
		return &opts
	}
}

// ContextHandler provides an entrypoint into executing graphQL queries with a
// user-provided context.
func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	// get query
	opts := NewRequestOptions(r)

	// execute graphql query
	params := graphql.Params{
		Schema:         *h.Schema,
		RequestString:  opts.Query,
		VariableValues: opts.Variables,
		OperationName:  opts.OperationName,
		Context:        ctx,
	}
	result := graphql.Do(params)

	if h.graphiql {
		acceptHeader := r.Header.Get("Accept")
		_, raw := r.URL.Query()["raw"]
		if !raw && !strings.Contains(acceptHeader, "application/json") && strings.Contains(acceptHeader, "text/html") {
			renderGraphiQL(w, params)
			return
		}
	}

	// use proper JSON Header
	w.Header().Add("Content-Type", "application/json; charset=utf-8")

	if h.pretty {
		w.WriteHeader(http.StatusOK)
		buff, _ := json.MarshalIndent(result, "", "\t")

		w.Write(buff)
	} else {
		w.WriteHeader(http.StatusOK)
		buff, _ := json.Marshal(result)

		w.Write(buff)
	}
}

// ServeHTTP provides an entrypoint into executing graphQL queries.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.ContextHandler(r.Context(), w, r)
}

type Config struct {
	Schema   *graphql.Schema
	Pretty   bool
	GraphiQL bool
}

func NewConfig() *Config {
	return &Config{
		Schema:   nil,
		Pretty:   true,
		GraphiQL: true,
	}
}

func New(p *Config) *Handler {
	if p == nil {
		p = NewConfig()
	}
	if p.Schema == nil {
		panic("undefined GraphQL schema")
	}

	return &Handler{
		Schema:   p.Schema,
		pretty:   p.Pretty,
		graphiql: p.GraphiQL,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"github.com/golang/groupcache"
	"github.com/gorilla/mux"
	"io"
	"math/rand"
	"net/http"
	"os"
	"time"
	"vip/fetch"
)

type UploadResponse struct {
	Url string `json:"url"`
}

type verifyAuth func(http.ResponseWriter, *http.Request)

func (h verifyAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Enable cross-origin requests
	if domain := os.Getenv("ALLOWED_ORIGIN"); domain != "" {
		if origin := r.Header.Get("Origin"); origin == domain {
			w.Header().Set("Access-Control-Allow-Origin", origin)
			w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
			w.Header().Set("Access-Control-Allow-Headers",
				"Accept, Content-Type, Content-Length, Accept-Encoding, X-Vip-Token, Authorization")
		}
	} else {
		auth := r.Header.Get("X-Vip-Token")
		if auth != authToken {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
	}

	if r.Method == "OPTIONS" {
		return
	}

	h(w, r)
}

func fileKey(bucket string) string {
	seed := rand.New(rand.NewSource(time.Now().UnixNano()))
	key := fmt.Sprintf("%d-%s-%d", seed.Int63(), bucket, time.Now().UnixNano())

	hash := md5.New()
	io.WriteString(hash, key)
	return fmt.Sprintf("%x", hash.Sum(nil))
}

func handleImageRequest(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
	}

	w.Header().Set("Cache-Control", "public, max-age=31536000")

	// Client is checking for a cached URI, assume it is valid
	// and return a 304
	if r.Header.Get("If-Modified-Since") != "" {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	gc := fetch.RequestContext(r)

	var data []byte
	err := cache.Get(gc, gc.CacheKey(), groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	w.Header().Set("Content-Type", http.DetectContentType(data))
	http.ServeContent(w, r, gc.ImageId, time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), bytes.NewReader(data))
}

func handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

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

	// Set a hard 5mb limit on files
	limit := 5
	if r.ContentLength > limit<<20 {
		errMsg := fmt.Printf("The file size limit is %dMB.\n", limit)
		http.Error(w, errMsg, http.StatusRequestEntityTooLarge)
		return
	}

	key := fileKey(bucket)
	err := storage.PutReader(bucket, key, r.Body,
		r.ContentLength, r.Header.Get("Content-Type"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	uri := r.URL

	if r.URL.Host == "" {
		uri.Host = os.Getenv("URI_HOSTNAME")
		if *secure {
			uri.Scheme = "https"
		} else {
			uri.Scheme = "http"
		}
	}

	uri.Path = fmt.Sprintf("%s/%s", bucket, key)

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

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(UploadResponse{
		Url: uri.String(),
	})
}

func handlePing(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "pong")
}
<commit_msg>fix bugs<commit_after>package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"github.com/golang/groupcache"
	"github.com/gorilla/mux"
	"io"
	"math/rand"
	"net/http"
	"os"
	"time"
	"vip/fetch"
)

type UploadResponse struct {
	Url string `json:"url"`
}

type verifyAuth func(http.ResponseWriter, *http.Request)

func (h verifyAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Enable cross-origin requests
	if domain := os.Getenv("ALLOWED_ORIGIN"); domain != "" {
		if origin := r.Header.Get("Origin"); origin == domain {
			w.Header().Set("Access-Control-Allow-Origin", origin)
			w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
			w.Header().Set("Access-Control-Allow-Headers",
				"Accept, Content-Type, Content-Length, Accept-Encoding, X-Vip-Token, Authorization")
		}
	} else {
		auth := r.Header.Get("X-Vip-Token")
		if auth != authToken {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
	}

	if r.Method == "OPTIONS" {
		return
	}

	h(w, r)
}

func fileKey(bucket string) string {
	seed := rand.New(rand.NewSource(time.Now().UnixNano()))
	key := fmt.Sprintf("%d-%s-%d", seed.Int63(), bucket, time.Now().UnixNano())

	hash := md5.New()
	io.WriteString(hash, key)
	return fmt.Sprintf("%x", hash.Sum(nil))
}

func handleImageRequest(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
	}

	w.Header().Set("Cache-Control", "public, max-age=31536000")

	// Client is checking for a cached URI, assume it is valid
	// and return a 304
	if r.Header.Get("If-Modified-Since") != "" {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	gc := fetch.RequestContext(r)

	var data []byte
	err := cache.Get(gc, gc.CacheKey(), groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	w.Header().Set("Content-Type", http.DetectContentType(data))
	http.ServeContent(w, r, gc.ImageId, time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), bytes.NewReader(data))
}

func handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

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

	// Set a hard 5mb limit on files
	var limit int64 = 5
	if r.ContentLength > limit<<20 {
		http.Error(w, fmt.Sprintf("The file size limit is %dMB.\n", limit), http.StatusRequestEntityTooLarge)
		return
	}

	key := fileKey(bucket)
	err := storage.PutReader(bucket, key, r.Body,
		r.ContentLength, r.Header.Get("Content-Type"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	uri := r.URL

	if r.URL.Host == "" {
		uri.Host = os.Getenv("URI_HOSTNAME")
		if *secure {
			uri.Scheme = "https"
		} else {
			uri.Scheme = "http"
		}
	}

	uri.Path = fmt.Sprintf("%s/%s", bucket, key)

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

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(UploadResponse{
		Url: uri.String(),
	})
}

func handlePing(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "pong")
}
<|endoftext|>


<commit_before>package dt

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strconv"
	"sync"

	"github.com/xlvector/hector/core"
)

type RandomForestParams struct {
	TreeCount    int
	FeatureCount float64
}

type RandomForest struct {
	trees               []*Tree
	params              RandomForestParams
	cart                CART
	continuous_features bool
}

func (self *RandomForest) SaveModel(path string) {
	file, _ := os.Create(path)
	defer file.Close()
	for _, tree := range self.trees {
		buf := tree.ToString()
		file.Write(buf)
		file.WriteString("\n#\n")
	}
}

func (self *RandomForest) LoadModel(path string) {
	file, _ := os.Open(path)
	defer file.Close()

	self.trees = []*Tree{}
	reader := bufio.NewReader(file)
	text := ""
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			break
		}
		if line == "#" {
			tree := Tree{}
			tree.FromString(text)
			log.Println("begin build tree")
			self.trees = append(self.trees, &tree)
			text = ""
		} else {
			text += line + "\n"
		}
	}
	log.Println("rf tree count :", len(self.trees))
}

func (dt *RandomForest) Init(params map[string]string) {
	dt.trees = []*Tree{}
	dt.cart.Init(params)
	tree_count, _ := strconv.ParseInt(params["tree-count"], 10, 64)
	feature_count, _ := strconv.ParseFloat(params["feature-count"], 64)
	dt.params.TreeCount = int(tree_count)
	dt.params.FeatureCount = feature_count
}

func (dt *RandomForest) Train(dataset *core.DataSet) {
	samples := []*core.MapBasedSample{}
	feature_weights := make(map[int64]float64)
	for _, sample := range dataset.Samples {
		if !dt.continuous_features {
			for _, f := range sample.Features {
				_, ok := feature_weights[f.Id]
				if !ok {
					feature_weights[f.Id] = f.Value
				}
				if feature_weights[f.Id] != f.Value {
					dt.continuous_features = true
				}
			}
		}
		msample := sample.ToMapBasedSample()
		samples = append(samples, msample)
	}
	dt.cart.continuous_features = dt.continuous_features

	trees := make(chan *Tree, dt.params.TreeCount)
	var wait sync.WaitGroup
	wait.Add(dt.params.TreeCount)

	for i := 0; i < dt.params.TreeCount; i++ {

		go func() {
			tree := dt.cart.SingleTreeBuild(samples, dt.params.FeatureCount, true)
			trees <- &tree
			fmt.Printf(".")
			wait.Done()
		}()
	}
	wait.Wait()
	fmt.Println()
	close(trees)
	for tree := range trees {
		dt.trees = append(dt.trees, tree)
	}
}

func (dt *RandomForest) Predict(sample *core.Sample) float64 {
	msample := sample.ToMapBasedSample()
	predictions := 0.0
	total := 0.0
	for _, tree := range dt.trees {
		node, _ := PredictBySingleTree(tree, msample)
		predictions += node.prediction.GetValue(1)
		total += 1.0
	}
	return predictions / total
}

func (dt *RandomForest) PredictMultiClass(sample *core.Sample) *core.ArrayVector {
	msample := sample.ToMapBasedSample()
	predictions := core.NewArrayVector()
	total := 0.0
	for _, tree := range dt.trees {
		node, _ := PredictBySingleTree(tree, msample)
		predictions.AddVector(node.prediction, 1.0)
		total += 1.0
	}
	predictions.Scale(1.0 / total)
	return predictions
}
<commit_msg>add log<commit_after>package dt

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strconv"
	"sync"

	"github.com/xlvector/hector/core"
)

type RandomForestParams struct {
	TreeCount    int
	FeatureCount float64
}

type RandomForest struct {
	trees               []*Tree
	params              RandomForestParams
	cart                CART
	continuous_features bool
}

func (self *RandomForest) SaveModel(path string) {
	file, _ := os.Create(path)
	defer file.Close()
	for _, tree := range self.trees {
		buf := tree.ToString()
		file.Write(buf)
		file.WriteString("\n#\n")
	}
}

func (self *RandomForest) LoadModel(path string) {
	file, _ := os.Open(path)
	defer file.Close()

	self.trees = []*Tree{}
	reader := bufio.NewReader(file)
	text := ""
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			break
		}
		log.Println(line)
		if line == "#" {
			tree := Tree{}
			tree.FromString(text)
			log.Println("begin build tree")
			self.trees = append(self.trees, &tree)
			text = ""
		} else {
			text += line + "\n"
		}
	}
	log.Println("rf tree count :", len(self.trees))
}

func (dt *RandomForest) Init(params map[string]string) {
	dt.trees = []*Tree{}
	dt.cart.Init(params)
	tree_count, _ := strconv.ParseInt(params["tree-count"], 10, 64)
	feature_count, _ := strconv.ParseFloat(params["feature-count"], 64)
	dt.params.TreeCount = int(tree_count)
	dt.params.FeatureCount = feature_count
}

func (dt *RandomForest) Train(dataset *core.DataSet) {
	samples := []*core.MapBasedSample{}
	feature_weights := make(map[int64]float64)
	for _, sample := range dataset.Samples {
		if !dt.continuous_features {
			for _, f := range sample.Features {
				_, ok := feature_weights[f.Id]
				if !ok {
					feature_weights[f.Id] = f.Value
				}
				if feature_weights[f.Id] != f.Value {
					dt.continuous_features = true
				}
			}
		}
		msample := sample.ToMapBasedSample()
		samples = append(samples, msample)
	}
	dt.cart.continuous_features = dt.continuous_features

	trees := make(chan *Tree, dt.params.TreeCount)
	var wait sync.WaitGroup
	wait.Add(dt.params.TreeCount)

	for i := 0; i < dt.params.TreeCount; i++ {

		go func() {
			tree := dt.cart.SingleTreeBuild(samples, dt.params.FeatureCount, true)
			trees <- &tree
			fmt.Printf(".")
			wait.Done()
		}()
	}
	wait.Wait()
	fmt.Println()
	close(trees)
	for tree := range trees {
		dt.trees = append(dt.trees, tree)
	}
}

func (dt *RandomForest) Predict(sample *core.Sample) float64 {
	msample := sample.ToMapBasedSample()
	predictions := 0.0
	total := 0.0
	for _, tree := range dt.trees {
		node, _ := PredictBySingleTree(tree, msample)
		predictions += node.prediction.GetValue(1)
		total += 1.0
	}
	return predictions / total
}

func (dt *RandomForest) PredictMultiClass(sample *core.Sample) *core.ArrayVector {
	msample := sample.ToMapBasedSample()
	predictions := core.NewArrayVector()
	total := 0.0
	for _, tree := range dt.trees {
		node, _ := PredictBySingleTree(tree, msample)
		predictions.AddVector(node.prediction, 1.0)
		total += 1.0
	}
	predictions.Scale(1.0 / total)
	return predictions
}
<|endoftext|>


<commit_before>package hashmap

type HashMapNode struct {
	key   string
	value int // this should be a pointer to any type
}

// has an array of HashMapNodes
type HashMap struct {
	size    int
	count   int
	buckets [][]HashMapNode
}

/** PRIVATE METHODS **/

// returns the index at which the key needs to go
// TODO: Do we need to typecast again?
func (h *HashMap) getIndex(key string) int {
	return int(hash(key)) % h.size
}

// Implements the Jenkins hash function
// TODO: What should the return type be?
func hash(key string) uint32 {
	var h uint32 = 0
	for _, c := range key {
		h += uint32(c)
		h += (h << 10)
		h ^= (h >> 6)
	}
	h += (h << 3)
	h ^= (h >> 11)
	h += (h << 15)
	return h
}

/** PUBLIC METHODS **/

// Constuctor that returns a new HashMap
func NewHashMap(size int) *HashMap {
	h := new(HashMap)
	h.size = size
	h.count = 0
	h.buckets = make([][]HashMapNode, size)
	for i := range h.buckets {
		h.buckets[i] = make([]HashMapNode, 1)
	}
	return h
}

// gets the value associated with a key in the hashmap
// returns the value to be true / false depending on whether
// the key exists in the hashmap
func (h *HashMap) Get(key string) (int, bool) {
	index := h.getIndex(key)
	chain := h.buckets[index]
	for _, node := range chain {
		if node.key == key {
			return node.value, true
		}
	}
	return 0, false
}

// sets the value for an associated key in the hashmap
func (h *HashMap) Set(key string, value int) bool {
	index := h.getIndex(key)
	chain := h.buckets[index]
	found := false
	for _, node := range chain {
		// if the key already exists
		if node.key == key {
			node.value = value
			found = true
		}
	}
	if found { // hashmap has been updated
		return true
	}
	if h.size == h.count { // hashmap is full
		return false
	}
	// add a new node
	node := HashMapNode{key: key, value: value}
	chain = append(chain, node)
	h.buckets[index] = chain
	h.count += 1
	return true
}

func (h *HashMap) Delete(key string) (int, bool) {
	index := h.getIndex(key)
	chain := h.buckets[index]

	// start a search for the key
	found := false
	var location, value int
	for loc, node := range chain {
		if node.key == key {
			found = true
			location = loc
			value = node.value
		}
	}

	// if found delete the elem from the slice
	if found {
		h.count -= 1
		N := len(chain) - 1
		chain[location], chain[N] = chain[N], chain[location]
		chain = chain[:N]
		h.buckets[index] = chain
		return value, true
	}

	// if not found return false
	return 0, false
}

// returns the load factor of the hashmap
func (h *HashMap) Load() float32 {
	return float32(h.count) / float32(h.size)
}
<commit_msg>comments<commit_after>package hashmap

// TODO: The value 'generic'
type HashMapNode struct {
	key   string
	value int
}

// HashMap implemented with a fixed size.
// Usese chaining to resolve collisions.
type HashMap struct {
	size    int
	count   int
	buckets [][]HashMapNode
}

/** PRIVATE METHODS **/

// returns the index at which the key needs to go
// TODO: Do we need to typecast again?
func (h *HashMap) getIndex(key string) int {
	return int(hash(key)) % h.size
}

// Implements the Jenkins hash function
// TODO: What should the return type be?
func hash(key string) uint32 {
	var h uint32 = 0
	for _, c := range key {
		h += uint32(c)
		h += (h << 10)
		h ^= (h >> 6)
	}
	h += (h << 3)
	h ^= (h >> 11)
	h += (h << 15)
	return h
}

/** PUBLIC METHODS **/

// Constuctor that returns a new HashMap
func NewHashMap(size int) *HashMap {
	h := new(HashMap)
	h.size = size
	h.count = 0
	h.buckets = make([][]HashMapNode, size)
	for i := range h.buckets {
		h.buckets[i] = make([]HashMapNode, 1)
	}
	return h
}

// gets the value associated with a key in the hashmap
// returns the value to be true / false depending on whether
// the key exists in the hashmap
func (h *HashMap) Get(key string) (int, bool) {
	index := h.getIndex(key)
	chain := h.buckets[index]
	for _, node := range chain {
		if node.key == key {
			return node.value, true
		}
	}
	return 0, false
}

// sets the value for an associated key in the hashmap
func (h *HashMap) Set(key string, value int) bool {
	index := h.getIndex(key)
	chain := h.buckets[index]
	found := false
	for _, node := range chain {
		// if the key already exists
		if node.key == key {
			node.value = value
			found = true
		}
	}
	if found { // hashmap has been updated
		return true
	}
	if h.size == h.count { // hashmap is full
		return false
	}
	// add a new node
	node := HashMapNode{key: key, value: value}
	chain = append(chain, node)
	h.buckets[index] = chain
	h.count += 1
	return true
}

func (h *HashMap) Delete(key string) (int, bool) {
	index := h.getIndex(key)
	chain := h.buckets[index]

	// start a search for the key
	found := false
	var location, value int
	for loc, node := range chain {
		if node.key == key {
			found = true
			location = loc
			value = node.value
		}
	}

	// if found delete the elem from the slice
	if found {
		h.count -= 1
		N := len(chain) - 1
		chain[location], chain[N] = chain[N], chain[location]
		chain = chain[:N]
		h.buckets[index] = chain
		return value, true
	}

	// if not found return false
	return 0, false
}

// returns the load factor of the hashmap
func (h *HashMap) Load() float32 {
	return float32(h.count) / float32(h.size)
}
<|endoftext|>


<commit_before>package provision

import (
	"fmt"
	"io/ioutil"
	"net/url"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/rancher/machine/libmachine/auth"
	"github.com/rancher/machine/libmachine/cert"
	"github.com/rancher/machine/libmachine/engine"
	"github.com/rancher/machine/libmachine/log"
	"github.com/rancher/machine/libmachine/mcnutils"
	"github.com/rancher/machine/libmachine/provision/serviceaction"
)

type DockerOptions struct {
	EngineOptions     string
	EngineOptionsPath string
}

func installDockerGeneric(p Provisioner, baseURL string) error {
	if strings.EqualFold(baseURL, "none") {
		log.Info("Skipping Docker installation")
		return nil
	}
	// install docker - until cloudinit we use ubuntu everywhere so we
	// just install it using the docker repos
	log.Infof("Installing Docker from: %s", baseURL)
	if output, err := p.SSHCommand(fmt.Sprintf("if ! type docker; then curl -sSL %s | sh -; fi", baseURL)); err != nil {
		return fmt.Errorf("Error installing Docker: %s", output)
	}

	return nil
}

func makeDockerOptionsDir(p Provisioner) error {
	dockerDir := p.GetDockerOptionsDir()
	if _, err := p.SSHCommand(fmt.Sprintf("sudo mkdir -p %s", dockerDir)); err != nil {
		return err
	}

	return nil
}

func setRemoteAuthOptions(p Provisioner) auth.Options {
	dockerDir := p.GetDockerOptionsDir()
	authOptions := p.GetAuthOptions()

	// due to windows clients, we cannot use filepath.Join as the paths
	// will be mucked on the linux hosts
	authOptions.CaCertRemotePath = path.Join(dockerDir, "ca.pem")
	authOptions.ServerCertRemotePath = path.Join(dockerDir, "server.pem")
	authOptions.ServerKeyRemotePath = path.Join(dockerDir, "server-key.pem")

	return authOptions
}

func ConfigureAuth(p Provisioner) error {
	var (
		err error
	)

	driver := p.GetDriver()
	machineName := driver.GetMachineName()
	authOptions := p.GetAuthOptions()
	swarmOptions := p.GetSwarmOptions()
	org := mcnutils.GetUsername() + "." + machineName
	bits := 2048

	ip, err := driver.GetIP()
	if err != nil {
		return err
	}

	log.Info("Copying certs to the local machine directory...")

	if err := mcnutils.CopyFile(authOptions.CaCertPath, filepath.Join(authOptions.StorePath, "ca.pem")); err != nil {
		return fmt.Errorf("Copying ca.pem to machine dir failed: %s", err)
	}

	if err := mcnutils.CopyFile(authOptions.ClientCertPath, filepath.Join(authOptions.StorePath, "cert.pem")); err != nil {
		return fmt.Errorf("Copying cert.pem to machine dir failed: %s", err)
	}

	if err := mcnutils.CopyFile(authOptions.ClientKeyPath, filepath.Join(authOptions.StorePath, "key.pem")); err != nil {
		return fmt.Errorf("Copying key.pem to machine dir failed: %s", err)
	}

	// The Host IP is always added to the certificate's SANs list
	hosts := append(authOptions.ServerCertSANs, ip, "localhost")
	log.Debugf("generating server cert: %s ca-key=%s private-key=%s org=%s san=%s",
		authOptions.ServerCertPath,
		authOptions.CaCertPath,
		authOptions.CaPrivateKeyPath,
		org,
		hosts,
	)

	// TODO: Switch to passing just authOptions to this func
	// instead of all these individual fields
	err = cert.GenerateCert(&cert.Options{
		Hosts:       hosts,
		CertFile:    authOptions.ServerCertPath,
		KeyFile:     authOptions.ServerKeyPath,
		CAFile:      authOptions.CaCertPath,
		CAKeyFile:   authOptions.CaPrivateKeyPath,
		Org:         org,
		Bits:        bits,
		SwarmMaster: swarmOptions.Master,
	})

	if err != nil {
		return fmt.Errorf("error generating server cert: %s", err)
	}

	if err := p.Service("docker", serviceaction.Stop); err != nil {
		return err
	}

	if _, err := p.SSHCommand(`if [ ! -z "$(ip link show docker0)" ]; then sudo ip link delete docker0; fi`); err != nil {
		return err
	}

	// upload certs and configure TLS auth
	caCert, err := ioutil.ReadFile(authOptions.CaCertPath)
	if err != nil {
		return err
	}

	serverCert, err := ioutil.ReadFile(authOptions.ServerCertPath)
	if err != nil {
		return err
	}
	serverKey, err := ioutil.ReadFile(authOptions.ServerKeyPath)
	if err != nil {
		return err
	}

	log.Info("Copying certs to the remote machine...")

	// printf will choke if we don't pass a format string because of the
	// dashes, so that's the reason for the '%%s'
	certTransferCmdFmt := "printf '%%s' '%s' | sudo tee %s"

	// These ones are for Jessie and Mike <3 <3 <3
	if _, err := p.SSHCommand(fmt.Sprintf(certTransferCmdFmt, string(caCert), authOptions.CaCertRemotePath)); err != nil {
		return err
	}

	if _, err := p.SSHCommand(fmt.Sprintf(certTransferCmdFmt, string(serverCert), authOptions.ServerCertRemotePath)); err != nil {
		return err
	}

	if _, err := p.SSHCommand(fmt.Sprintf(certTransferCmdFmt, string(serverKey), authOptions.ServerKeyRemotePath)); err != nil {
		return err
	}

	dockerURL, err := driver.GetURL()
	if err != nil {
		return err
	}
	u, err := url.Parse(dockerURL)
	if err != nil {
		return err
	}
	dockerPort := engine.DefaultPort
	parts := strings.Split(u.Host, ":")
	if len(parts) == 2 {
		dPort, err := strconv.Atoi(parts[1])
		if err != nil {
			return err
		}
		dockerPort = dPort
	}

	dkrcfg, err := p.GenerateDockerOptions(dockerPort)
	if err != nil {
		return err
	}

	log.Info("Setting Docker configuration on the remote daemon...")

	if _, err = p.SSHCommand(fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | sudo tee %s", path.Dir(dkrcfg.EngineOptionsPath), dkrcfg.EngineOptions, dkrcfg.EngineOptionsPath)); err != nil {
		return err
	}

	if err := p.Service("docker", serviceaction.Start); err != nil {
		return err
	}

	return WaitForDocker(p, dockerPort)
}

func matchNetstatOut(reDaemonListening, netstatOut string) bool {
	// TODO: I would really prefer this be a Scanner directly on
	// the STDOUT of the executed command than to do all the string
	// manipulation hokey-pokey.
	//
	// TODO: Unit test this matching.
	for _, line := range strings.Split(netstatOut, "\n") {
		match, err := regexp.MatchString(reDaemonListening, line)
		if err != nil {
			log.Warnf("Regex warning: %s", err)
		}
		if match && line != "" {
			return true
		}
	}

	return false
}

func decideStorageDriver(p Provisioner, defaultDriver, suppliedDriver string) (string, error) {
	if suppliedDriver != "" {
		return suppliedDriver, nil
	}
	bestSuitedDriver := ""

	defer func() {
		if bestSuitedDriver != "" {
			log.Debugf("No storagedriver specified, using %s\n", bestSuitedDriver)
		}
	}()

	if defaultDriver != DefaultStorageDriver {
		bestSuitedDriver = defaultDriver
	} else {
		remoteFilesystemType, err := getFilesystemType(p, "/var/lib")
		if err != nil {
			return "", err
		}
		if remoteFilesystemType == "btrfs" {
			bestSuitedDriver = "btrfs"
		} else {
			bestSuitedDriver = DefaultStorageDriver
		}
	}
	return bestSuitedDriver, nil

}

func getFilesystemType(p Provisioner, directory string) (string, error) {
	statCommandOutput, err := p.SSHCommand("stat -f -c %T " + directory)
	if err != nil {
		err = fmt.Errorf("Error looking up filesystem type: %s", err)
		return "", err
	}

	fstype := strings.TrimSpace(statCommandOutput)
	return fstype, nil
}

func checkDaemonUp(p Provisioner, dockerPort int) func() bool {
	reDaemonListening := fmt.Sprintf(":%d\\s+.*:.*", dockerPort)
	return func() bool {
		// HACK: Check netstat's output to see if anyone's listening on the Docker API port.
		netstatOut, err := p.SSHCommand("if ! type netstat 1>/dev/null; then ss -tln; else netstat -tln; fi")
		if err != nil {
			log.Warnf("Error running SSH command: %s", err)
			return false
		}

		return matchNetstatOut(reDaemonListening, netstatOut)
	}
}

func WaitForDocker(p Provisioner, dockerPort int) error {
	if err := mcnutils.WaitForSpecific(checkDaemonUp(p, dockerPort), 10, 3*time.Second); err != nil {
		return NewErrDaemonAvailable(err)
	}

	return nil
}

// DockerClientVersion returns the version of the Docker client on the host
// that ssh is connected to, e.g. "1.12.1".
func DockerClientVersion(ssh SSHCommander) (string, error) {
	// `docker version --format {{.Client.Version}}` would be preferable, but
	// that fails if the server isn't running yet.
	//
	// output is expected to be something like
	//
	//     Docker version 1.12.1, build 7a86f89
	output, err := ssh.SSHCommand("docker --version")
	if err != nil {
		return "", err
	}

	words := strings.Fields(output)
	if len(words) < 3 || words[0] != "Docker" || words[1] != "version" {
		return "", fmt.Errorf("DockerClientVersion: cannot parse version string from %q", output)
	}

	return strings.TrimRight(words[2], ","), nil
}

func waitForLockAptGetUpdate(ssh SSHCommander) error {
	return waitForLock(ssh, "sudo apt-get update")
}

func waitForLock(ssh SSHCommander, cmd string) error {
	var sshErr error
	err := mcnutils.WaitFor(func() bool {
		_, sshErr = ssh.SSHCommand(cmd)
		if sshErr != nil {
			if strings.Contains(sshErr.Error(), "Could not get lock") {
				sshErr = nil
				return false
			}
			return true
		}
		return true
	})
	if sshErr != nil {
		return fmt.Errorf("Error running %q: %s", cmd, sshErr)
	}
	if err != nil {
		return fmt.Errorf("Failed to obtain lock: %s", err)
	}
	return nil
}
<commit_msg>Forcing docker restart once auth is configured<commit_after>package provision

import (
	"fmt"
	"io/ioutil"
	"net/url"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/rancher/machine/libmachine/auth"
	"github.com/rancher/machine/libmachine/cert"
	"github.com/rancher/machine/libmachine/engine"
	"github.com/rancher/machine/libmachine/log"
	"github.com/rancher/machine/libmachine/mcnutils"
	"github.com/rancher/machine/libmachine/provision/serviceaction"
)

type DockerOptions struct {
	EngineOptions     string
	EngineOptionsPath string
}

func installDockerGeneric(p Provisioner, baseURL string) error {
	if strings.EqualFold(baseURL, "none") {
		log.Info("Skipping Docker installation")
		return nil
	}
	// install docker - until cloudinit we use ubuntu everywhere so we
	// just install it using the docker repos
	log.Infof("Installing Docker from: %s", baseURL)
	if output, err := p.SSHCommand(fmt.Sprintf("if ! type docker; then curl -sSL %s | sh -; fi", baseURL)); err != nil {
		return fmt.Errorf("Error installing Docker: %s", output)
	}

	return nil
}

func makeDockerOptionsDir(p Provisioner) error {
	dockerDir := p.GetDockerOptionsDir()
	if _, err := p.SSHCommand(fmt.Sprintf("sudo mkdir -p %s", dockerDir)); err != nil {
		return err
	}

	return nil
}

func setRemoteAuthOptions(p Provisioner) auth.Options {
	dockerDir := p.GetDockerOptionsDir()
	authOptions := p.GetAuthOptions()

	// due to windows clients, we cannot use filepath.Join as the paths
	// will be mucked on the linux hosts
	authOptions.CaCertRemotePath = path.Join(dockerDir, "ca.pem")
	authOptions.ServerCertRemotePath = path.Join(dockerDir, "server.pem")
	authOptions.ServerKeyRemotePath = path.Join(dockerDir, "server-key.pem")

	return authOptions
}

func ConfigureAuth(p Provisioner) error {
	var (
		err error
	)

	driver := p.GetDriver()
	machineName := driver.GetMachineName()
	authOptions := p.GetAuthOptions()
	swarmOptions := p.GetSwarmOptions()
	org := mcnutils.GetUsername() + "." + machineName
	bits := 2048

	ip, err := driver.GetIP()
	if err != nil {
		return err
	}

	log.Info("Copying certs to the local machine directory...")

	if err := mcnutils.CopyFile(authOptions.CaCertPath, filepath.Join(authOptions.StorePath, "ca.pem")); err != nil {
		return fmt.Errorf("Copying ca.pem to machine dir failed: %s", err)
	}

	if err := mcnutils.CopyFile(authOptions.ClientCertPath, filepath.Join(authOptions.StorePath, "cert.pem")); err != nil {
		return fmt.Errorf("Copying cert.pem to machine dir failed: %s", err)
	}

	if err := mcnutils.CopyFile(authOptions.ClientKeyPath, filepath.Join(authOptions.StorePath, "key.pem")); err != nil {
		return fmt.Errorf("Copying key.pem to machine dir failed: %s", err)
	}

	// The Host IP is always added to the certificate's SANs list
	hosts := append(authOptions.ServerCertSANs, ip, "localhost")
	log.Debugf("generating server cert: %s ca-key=%s private-key=%s org=%s san=%s",
		authOptions.ServerCertPath,
		authOptions.CaCertPath,
		authOptions.CaPrivateKeyPath,
		org,
		hosts,
	)

	// TODO: Switch to passing just authOptions to this func
	// instead of all these individual fields
	err = cert.GenerateCert(&cert.Options{
		Hosts:       hosts,
		CertFile:    authOptions.ServerCertPath,
		KeyFile:     authOptions.ServerKeyPath,
		CAFile:      authOptions.CaCertPath,
		CAKeyFile:   authOptions.CaPrivateKeyPath,
		Org:         org,
		Bits:        bits,
		SwarmMaster: swarmOptions.Master,
	})

	if err != nil {
		return fmt.Errorf("error generating server cert: %s", err)
	}

	if err := p.Service("docker", serviceaction.Stop); err != nil {
		return err
	}

	if _, err := p.SSHCommand(`if [ ! -z "$(ip link show docker0)" ]; then sudo ip link delete docker0; fi`); err != nil {
		return err
	}

	// upload certs and configure TLS auth
	caCert, err := ioutil.ReadFile(authOptions.CaCertPath)
	if err != nil {
		return err
	}

	serverCert, err := ioutil.ReadFile(authOptions.ServerCertPath)
	if err != nil {
		return err
	}
	serverKey, err := ioutil.ReadFile(authOptions.ServerKeyPath)
	if err != nil {
		return err
	}

	log.Info("Copying certs to the remote machine...")

	// printf will choke if we don't pass a format string because of the
	// dashes, so that's the reason for the '%%s'
	certTransferCmdFmt := "printf '%%s' '%s' | sudo tee %s"

	// These ones are for Jessie and Mike <3 <3 <3
	if _, err := p.SSHCommand(fmt.Sprintf(certTransferCmdFmt, string(caCert), authOptions.CaCertRemotePath)); err != nil {
		return err
	}

	if _, err := p.SSHCommand(fmt.Sprintf(certTransferCmdFmt, string(serverCert), authOptions.ServerCertRemotePath)); err != nil {
		return err
	}

	if _, err := p.SSHCommand(fmt.Sprintf(certTransferCmdFmt, string(serverKey), authOptions.ServerKeyRemotePath)); err != nil {
		return err
	}

	dockerURL, err := driver.GetURL()
	if err != nil {
		return err
	}
	u, err := url.Parse(dockerURL)
	if err != nil {
		return err
	}
	dockerPort := engine.DefaultPort
	parts := strings.Split(u.Host, ":")
	if len(parts) == 2 {
		dPort, err := strconv.Atoi(parts[1])
		if err != nil {
			return err
		}
		dockerPort = dPort
	}

	dkrcfg, err := p.GenerateDockerOptions(dockerPort)
	if err != nil {
		return err
	}

	log.Info("Setting Docker configuration on the remote daemon...")

	if _, err = p.SSHCommand(fmt.Sprintf("sudo mkdir -p %s && printf %%s \"%s\" | sudo tee %s", path.Dir(dkrcfg.EngineOptionsPath), dkrcfg.EngineOptions, dkrcfg.EngineOptionsPath)); err != nil {
		return err
	}

	if err := p.Service("docker", serviceaction.Restart); err != nil {
		return err
	}

	return WaitForDocker(p, dockerPort)
}

func matchNetstatOut(reDaemonListening, netstatOut string) bool {
	// TODO: I would really prefer this be a Scanner directly on
	// the STDOUT of the executed command than to do all the string
	// manipulation hokey-pokey.
	//
	// TODO: Unit test this matching.
	for _, line := range strings.Split(netstatOut, "\n") {
		match, err := regexp.MatchString(reDaemonListening, line)
		if err != nil {
			log.Warnf("Regex warning: %s", err)
		}
		if match && line != "" {
			return true
		}
	}

	return false
}

func decideStorageDriver(p Provisioner, defaultDriver, suppliedDriver string) (string, error) {
	if suppliedDriver != "" {
		return suppliedDriver, nil
	}
	bestSuitedDriver := ""

	defer func() {
		if bestSuitedDriver != "" {
			log.Debugf("No storagedriver specified, using %s\n", bestSuitedDriver)
		}
	}()

	if defaultDriver != DefaultStorageDriver {
		bestSuitedDriver = defaultDriver
	} else {
		remoteFilesystemType, err := getFilesystemType(p, "/var/lib")
		if err != nil {
			return "", err
		}
		if remoteFilesystemType == "btrfs" {
			bestSuitedDriver = "btrfs"
		} else {
			bestSuitedDriver = DefaultStorageDriver
		}
	}
	return bestSuitedDriver, nil

}

func getFilesystemType(p Provisioner, directory string) (string, error) {
	statCommandOutput, err := p.SSHCommand("stat -f -c %T " + directory)
	if err != nil {
		err = fmt.Errorf("Error looking up filesystem type: %s", err)
		return "", err
	}

	fstype := strings.TrimSpace(statCommandOutput)
	return fstype, nil
}

func checkDaemonUp(p Provisioner, dockerPort int) func() bool {
	reDaemonListening := fmt.Sprintf(":%d\\s+.*:.*", dockerPort)
	return func() bool {
		// HACK: Check netstat's output to see if anyone's listening on the Docker API port.
		netstatOut, err := p.SSHCommand("if ! type netstat 1>/dev/null; then ss -tln; else netstat -tln; fi")
		if err != nil {
			log.Warnf("Error running SSH command: %s", err)
			return false
		}

		return matchNetstatOut(reDaemonListening, netstatOut)
	}
}

func WaitForDocker(p Provisioner, dockerPort int) error {
	if err := mcnutils.WaitForSpecific(checkDaemonUp(p, dockerPort), 10, 3*time.Second); err != nil {
		return NewErrDaemonAvailable(err)
	}

	return nil
}

// DockerClientVersion returns the version of the Docker client on the host
// that ssh is connected to, e.g. "1.12.1".
func DockerClientVersion(ssh SSHCommander) (string, error) {
	// `docker version --format {{.Client.Version}}` would be preferable, but
	// that fails if the server isn't running yet.
	//
	// output is expected to be something like
	//
	//     Docker version 1.12.1, build 7a86f89
	output, err := ssh.SSHCommand("docker --version")
	if err != nil {
		return "", err
	}

	words := strings.Fields(output)
	if len(words) < 3 || words[0] != "Docker" || words[1] != "version" {
		return "", fmt.Errorf("DockerClientVersion: cannot parse version string from %q", output)
	}

	return strings.TrimRight(words[2], ","), nil
}

func waitForLockAptGetUpdate(ssh SSHCommander) error {
	return waitForLock(ssh, "sudo apt-get update")
}

func waitForLock(ssh SSHCommander, cmd string) error {
	var sshErr error
	err := mcnutils.WaitFor(func() bool {
		_, sshErr = ssh.SSHCommand(cmd)
		if sshErr != nil {
			if strings.Contains(sshErr.Error(), "Could not get lock") {
				sshErr = nil
				return false
			}
			return true
		}
		return true
	})
	if sshErr != nil {
		return fmt.Errorf("Error running %q: %s", cmd, sshErr)
	}
	if err != nil {
		return fmt.Errorf("Failed to obtain lock: %s", err)
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"
)

// An instance of a backend server which will receive requests from the CDN.
// Implements the http.Handler interface with a modifiable handler so that
// tests can pass in their own functions to inspect requests and modify
// responses.
type CDNBackendServer struct {
	Name    string
	Port    int
	handler func(w http.ResponseWriter, r *http.Request)
	server  *httptest.Server
}

func (s *CDNBackendServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Backend-Name", s.Name)
	if r.Method == "HEAD" && r.URL.Path == "/" {
		w.Header().Set("PING", "PONG")
		return
	}

	s.handler(w, r)
}

func (s *CDNBackendServer) ResetHandler() {
	s.handler = func(w http.ResponseWriter, r *http.Request) {}
}

func (s *CDNBackendServer) SwitchHandler(h func(w http.ResponseWriter, r *http.Request)) {
	s.handler = h
}

func (s *CDNBackendServer) IsStarted() bool {
	return (s.server != nil)
}

func (s *CDNBackendServer) Stop() {
	s.server.Close()
	s.server = nil
}

func (s *CDNBackendServer) Start() {
	s.ResetHandler()

	addr := fmt.Sprintf(":%d", s.Port)
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatal(err)
	}

	s.server = httptest.NewUnstartedServer(s)
	s.server.Listener = ln
	s.server.Start()

	log.Printf("Started server on port %d", s.Port)
}

// Return a v4 (random) UUID string.
// This might not be strictly RFC4122 compliant, but it will do. Credit:
// https://groups.google.com/d/msg/golang-nuts/Rn13T6BZpgE/dBaYVJ4hB5gJ
func NewUUID() string {
	bs := make([]byte, 16)
	rand.Read(bs)
	bs[6] = (bs[6] & 0x0f) | 0x40
	bs[8] = (bs[8] & 0x3f) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:])
}

// Construct a new URL for edge. Always uses HTTPS. A random UUID is used in
// the path to ensure that it hasn't previously been cached. It is passed as
// a query param for / so that some of the tests can be run against a
// service that hasn't been configured to point at our test backends.
func NewUniqueEdgeURL() string {
	url := url.URL{
		Scheme: "https",
		Host:   *edgeHost,
		Path:   fmt.Sprintf("/?nocache=%s", NewUUID()),
	}

	return url.String()
}

// Construct a GET request (but not perform it) against edge. Uses
// NewUniqueEdgeURL() to ensure that it hasn't previously been cached. The
// request method field of the returned object can be later modified if
// required.
func NewUniqueEdgeGET(t *testing.T) *http.Request {
	url := NewUniqueEdgeURL()
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		t.Fatal(err)
	}

	return req
}

// Make an HTTP request using http.RoundTrip, which doesn't handle redirects
// or cookies, and return the response. If there are any errors then the
// calling test will be aborted so as not to operate on a nil response.
func RoundTripCheckError(t *testing.T, req *http.Request) *http.Response {
	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	return resp
}

// Reset all backends, ensuring that they are started, have the default
// handler function, and that the edge considers them healthy. It may take
// some time because we need to receive and respond to enough probe health
// checks to be considered up.
func ResetBackends(backends []*CDNBackendServer) {
	remainingBackendsStopped := false

	// Reverse priority order so that waitForBackend works.
	for i := len(backends); i > 0; i-- {
		backend := backends[i-1]

		if backend.IsStarted() {
			backend.ResetHandler()
		} else {
			if !remainingBackendsStopped {
				// Ensure all remaining unchecked backends are stopped so that
				// waitForBackend will work. We'll bring them back one-by-one.
				stopBackends(backends[0 : i-1])
				remainingBackendsStopped = true
			}

			backend.Start()
			err := waitForBackend(backend.Name)
			if err != nil {
				log.Fatal(err)
			}
		}
	}
}

// Ensure that a slice of backends are stopped.
func stopBackends(backends []*CDNBackendServer) {
	for _, backend := range backends {
		if backend.IsStarted() {
			backend.Stop()
		}
	}
}

// Wait for the backend to return with the header we expect. This is designed to
// confirm that requests are hitting this specific backend, rather than a lower-level
// backend that this overrides (for example, origin over a mirror)
//
func waitForBackend(expectedBackendName string) error {
	const maxRetries = 20
	const waitForCdnProbeToPropagate = time.Duration(5 * time.Second)
	const timeBetweenAttempts = time.Duration(2 * time.Second)

	var url string

	log.Printf("Checking health of %s...", expectedBackendName)
	for try := 0; try <= maxRetries; try++ {
		url = NewUniqueEdgeURL()
		req, _ := http.NewRequest("GET", url, nil)
		resp, err := client.RoundTrip(req)
		if err != nil {
			return err
		}
		if resp.Header.Get("Backend-Name") == expectedBackendName {
			if try != 0 {
				time.Sleep(waitForCdnProbeToPropagate)
			}
			log.Println(expectedBackendName + " is up!")
			return nil // all is well!
		}
		time.Sleep(timeBetweenAttempts)
	}

	return fmt.Errorf(
		"%s still not available after %d attempts",
		expectedBackendName,
		maxRetries,
	)

}

// Callback function to modify complete response.
type responseCallback func(w http.ResponseWriter)

// Wrapper for testRequestsCachedDuration() with a respTTL of zero.
// Meaning that the cached object doesn't expire.
func testRequestsCachedIndefinite(t *testing.T, respCB responseCallback) {
	testRequestsCachedDuration(t, respCB, time.Duration(0))
}

// Helper function to make three requests and test responses. If respTTL is:
//
//	- zero: no delay between requests, origin should only see one request,
//		and all response bodies should be identical (from cache).
//	- non-zero: first and second request without delay, origin should only
//		see one request and responses bodies should be identical, then after a
//		delay of respTTL + a buffer a third response should get a new response
//		directly from origin.
//
// A responseCallback, if not nil, will be called to modify the response
// before calling Write(body).
func testRequestsCachedDuration(t *testing.T, respCB responseCallback, respTTL time.Duration) {
	const responseCached = "first response"
	const responseNotCached = "subsequent response"
	var testCacheExpiry bool = respTTL > 0
	var respTTLWithBuffer time.Duration = respTTL + (respTTL / 4)
	var requestsExpectedCount int

	requestsReceivedCount := 0
	switch testCacheExpiry {
	case true:
		requestsExpectedCount = 2
	case false:
		requestsExpectedCount = 1
	}

	url := fmt.Sprintf("https://%s/%s", *edgeHost, NewUUID())
	req, _ := http.NewRequest("GET", url, nil)

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if respCB != nil {
			respCB(w)
		}

		if requestsReceivedCount == 0 {
			w.Write([]byte(responseCached))
		} else {
			w.Write([]byte(responseNotCached))
		}

		requestsReceivedCount++
	})

	for requestCount := 0; requestCount < 3; requestCount++ {
		if testCacheExpiry && requestCount == 2 {
			time.Sleep(respTTLWithBuffer)
		}

		resp := RoundTripCheckError(t, req)

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}

		var expectedBody string
		if testCacheExpiry && requestCount > 1 {
			expectedBody = responseNotCached
		} else {
			expectedBody = responseCached
		}

		if receivedBody := string(body); receivedBody != expectedBody {
			t.Errorf(
				"Incorrect response body. Expected %q, got %q",
				expectedBody,
				receivedBody,
			)
		}
	}

	if requestsReceivedCount != requestsExpectedCount {
		t.Errorf(
			"Origin received the wrong number of requests. Expected %d, got %d",
			requestsExpectedCount,
			requestsReceivedCount,
		)
	}
}

// Callback function to modify response headers.
type responseHeaderCallback func(h http.Header)

// Helper function to make three requests and verify that we get three
// unique and uncached responses back. A responseHeaderCallback, if not nil,
// will be called to modify the response headers.
func testThreeRequestsNotCached(t *testing.T, req *http.Request, headerCB responseHeaderCallback) {
	requestsReceivedCount := 0
	responseBodies := []string{
		"first response",
		"second response",
		"third response",
	}

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if headerCB != nil {
			headerCB(w.Header())
		}
		w.Write([]byte(responseBodies[requestsReceivedCount]))
		requestsReceivedCount++
	})

	for _, expectedBody := range responseBodies {
		resp := RoundTripCheckError(t, req)

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}

		if receivedBody := string(body); receivedBody != expectedBody {
			t.Errorf("Incorrect response body. Expected %q, got %q", expectedBody, receivedBody)
		}
	}
}
<commit_msg>Documentation for CDNBackendServer<commit_after>package main

import (
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"
)

// CDNBackendServer is a backend server which will receive and respond to
// requests from the CDN.
type CDNBackendServer struct {
	Name    string
	Port    int
	handler func(w http.ResponseWriter, r *http.Request)
	server  *httptest.Server
}

// ServeHTTP satisfies the http.HandlerFunc interface. Health check requests
// for `HEAD /` are always served 200 responses. Other requests are passed
// off to a custom handler provided by SwitchHandler.
func (s *CDNBackendServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Backend-Name", s.Name)
	if r.Method == "HEAD" && r.URL.Path == "/" {
		w.Header().Set("PING", "PONG")
		return
	}

	s.handler(w, r)
}

// ResetHandler sets the handler back to an empty function that will return
// a 200 response.
func (s *CDNBackendServer) ResetHandler() {
	s.handler = func(w http.ResponseWriter, r *http.Request) {}
}

// SwitchHandler sets the handler to a custom function. This is used by
// tests to pass in their own request inspection and response handler.
func (s *CDNBackendServer) SwitchHandler(h func(w http.ResponseWriter, r *http.Request)) {
	s.handler = h
}

// IsStarted checks whether the server is currently started.
func (s *CDNBackendServer) IsStarted() bool {
	return (s.server != nil)
}

// Stop closes all outstanding client connections and unbind the port.
// Resets server back to nil, as if the backend had been instantiated but
// Start() not called.
func (s *CDNBackendServer) Stop() {
	s.server.Close()
	s.server = nil
}

// Start resets the handler back to the default and starts the server on
// Port. It will exit immediately if it's unable to bind the port, due to
// permissions or a conflicting application.
func (s *CDNBackendServer) Start() {
	s.ResetHandler()

	addr := fmt.Sprintf(":%d", s.Port)
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatal(err)
	}

	s.server = httptest.NewUnstartedServer(s)
	s.server.Listener = ln
	s.server.Start()

	log.Printf("Started server on port %d", s.Port)
}

// Return a v4 (random) UUID string.
// This might not be strictly RFC4122 compliant, but it will do. Credit:
// https://groups.google.com/d/msg/golang-nuts/Rn13T6BZpgE/dBaYVJ4hB5gJ
func NewUUID() string {
	bs := make([]byte, 16)
	rand.Read(bs)
	bs[6] = (bs[6] & 0x0f) | 0x40
	bs[8] = (bs[8] & 0x3f) | 0x80

	return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:])
}

// Construct a new URL for edge. Always uses HTTPS. A random UUID is used in
// the path to ensure that it hasn't previously been cached. It is passed as
// a query param for / so that some of the tests can be run against a
// service that hasn't been configured to point at our test backends.
func NewUniqueEdgeURL() string {
	url := url.URL{
		Scheme: "https",
		Host:   *edgeHost,
		Path:   fmt.Sprintf("/?nocache=%s", NewUUID()),
	}

	return url.String()
}

// Construct a GET request (but not perform it) against edge. Uses
// NewUniqueEdgeURL() to ensure that it hasn't previously been cached. The
// request method field of the returned object can be later modified if
// required.
func NewUniqueEdgeGET(t *testing.T) *http.Request {
	url := NewUniqueEdgeURL()
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		t.Fatal(err)
	}

	return req
}

// Make an HTTP request using http.RoundTrip, which doesn't handle redirects
// or cookies, and return the response. If there are any errors then the
// calling test will be aborted so as not to operate on a nil response.
func RoundTripCheckError(t *testing.T, req *http.Request) *http.Response {
	resp, err := client.RoundTrip(req)
	if err != nil {
		t.Fatal(err)
	}

	return resp
}

// Reset all backends, ensuring that they are started, have the default
// handler function, and that the edge considers them healthy. It may take
// some time because we need to receive and respond to enough probe health
// checks to be considered up.
func ResetBackends(backends []*CDNBackendServer) {
	remainingBackendsStopped := false

	// Reverse priority order so that waitForBackend works.
	for i := len(backends); i > 0; i-- {
		backend := backends[i-1]

		if backend.IsStarted() {
			backend.ResetHandler()
		} else {
			if !remainingBackendsStopped {
				// Ensure all remaining unchecked backends are stopped so that
				// waitForBackend will work. We'll bring them back one-by-one.
				stopBackends(backends[0 : i-1])
				remainingBackendsStopped = true
			}

			backend.Start()
			err := waitForBackend(backend.Name)
			if err != nil {
				log.Fatal(err)
			}
		}
	}
}

// Ensure that a slice of backends are stopped.
func stopBackends(backends []*CDNBackendServer) {
	for _, backend := range backends {
		if backend.IsStarted() {
			backend.Stop()
		}
	}
}

// Wait for the backend to return with the header we expect. This is designed to
// confirm that requests are hitting this specific backend, rather than a lower-level
// backend that this overrides (for example, origin over a mirror)
//
func waitForBackend(expectedBackendName string) error {
	const maxRetries = 20
	const waitForCdnProbeToPropagate = time.Duration(5 * time.Second)
	const timeBetweenAttempts = time.Duration(2 * time.Second)

	var url string

	log.Printf("Checking health of %s...", expectedBackendName)
	for try := 0; try <= maxRetries; try++ {
		url = NewUniqueEdgeURL()
		req, _ := http.NewRequest("GET", url, nil)
		resp, err := client.RoundTrip(req)
		if err != nil {
			return err
		}
		if resp.Header.Get("Backend-Name") == expectedBackendName {
			if try != 0 {
				time.Sleep(waitForCdnProbeToPropagate)
			}
			log.Println(expectedBackendName + " is up!")
			return nil // all is well!
		}
		time.Sleep(timeBetweenAttempts)
	}

	return fmt.Errorf(
		"%s still not available after %d attempts",
		expectedBackendName,
		maxRetries,
	)

}

// Callback function to modify complete response.
type responseCallback func(w http.ResponseWriter)

// Wrapper for testRequestsCachedDuration() with a respTTL of zero.
// Meaning that the cached object doesn't expire.
func testRequestsCachedIndefinite(t *testing.T, respCB responseCallback) {
	testRequestsCachedDuration(t, respCB, time.Duration(0))
}

// Helper function to make three requests and test responses. If respTTL is:
//
//	- zero: no delay between requests, origin should only see one request,
//		and all response bodies should be identical (from cache).
//	- non-zero: first and second request without delay, origin should only
//		see one request and responses bodies should be identical, then after a
//		delay of respTTL + a buffer a third response should get a new response
//		directly from origin.
//
// A responseCallback, if not nil, will be called to modify the response
// before calling Write(body).
func testRequestsCachedDuration(t *testing.T, respCB responseCallback, respTTL time.Duration) {
	const responseCached = "first response"
	const responseNotCached = "subsequent response"
	var testCacheExpiry bool = respTTL > 0
	var respTTLWithBuffer time.Duration = respTTL + (respTTL / 4)
	var requestsExpectedCount int

	requestsReceivedCount := 0
	switch testCacheExpiry {
	case true:
		requestsExpectedCount = 2
	case false:
		requestsExpectedCount = 1
	}

	url := fmt.Sprintf("https://%s/%s", *edgeHost, NewUUID())
	req, _ := http.NewRequest("GET", url, nil)

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if respCB != nil {
			respCB(w)
		}

		if requestsReceivedCount == 0 {
			w.Write([]byte(responseCached))
		} else {
			w.Write([]byte(responseNotCached))
		}

		requestsReceivedCount++
	})

	for requestCount := 0; requestCount < 3; requestCount++ {
		if testCacheExpiry && requestCount == 2 {
			time.Sleep(respTTLWithBuffer)
		}

		resp := RoundTripCheckError(t, req)

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}

		var expectedBody string
		if testCacheExpiry && requestCount > 1 {
			expectedBody = responseNotCached
		} else {
			expectedBody = responseCached
		}

		if receivedBody := string(body); receivedBody != expectedBody {
			t.Errorf(
				"Incorrect response body. Expected %q, got %q",
				expectedBody,
				receivedBody,
			)
		}
	}

	if requestsReceivedCount != requestsExpectedCount {
		t.Errorf(
			"Origin received the wrong number of requests. Expected %d, got %d",
			requestsExpectedCount,
			requestsReceivedCount,
		)
	}
}

// Callback function to modify response headers.
type responseHeaderCallback func(h http.Header)

// Helper function to make three requests and verify that we get three
// unique and uncached responses back. A responseHeaderCallback, if not nil,
// will be called to modify the response headers.
func testThreeRequestsNotCached(t *testing.T, req *http.Request, headerCB responseHeaderCallback) {
	requestsReceivedCount := 0
	responseBodies := []string{
		"first response",
		"second response",
		"third response",
	}

	originServer.SwitchHandler(func(w http.ResponseWriter, r *http.Request) {
		if headerCB != nil {
			headerCB(w.Header())
		}
		w.Write([]byte(responseBodies[requestsReceivedCount]))
		requestsReceivedCount++
	})

	for _, expectedBody := range responseBodies {
		resp := RoundTripCheckError(t, req)

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}

		if receivedBody := string(body); receivedBody != expectedBody {
			t.Errorf("Incorrect response body. Expected %q, got %q", expectedBody, receivedBody)
		}
	}
}
<|endoftext|>


<commit_before>package lib

import (
	"fmt"
	"strings"
)

type tableColumn struct {
	ColumnName string
	ColumnType string
	Primary    bool
	Nullable   bool
}

func (t tableColumn) CapitalizedColumnName() string {
	var result string
	for _, tp := range strings.Split(t.ColumnName, "_") {
		tp = strings.ToUpper(tp[0:1]) + strings.ToLower(tp[1:])
		result = result + tp
	}
	return result
}

func (t tableColumn) TextRightHandConvert() string {
	switch t.MappedColumnType() {
	case "int", "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64":
		return "i, _ := strconv.Atoi(form_" + t.LowercaseColumnName() + "); parsedField := " + t.MappedColumnType() + "(i)"
	case "[]byte":
		return "parsedField:= []byte(form_" + t.LowercaseColumnName() + ")"
	case "bool":
		return "parsedField, _ := strconv.ParseBool(form_" + t.LowercaseColumnName() + ")"
	default:
		return "parsedField:= form_" + t.LowercaseColumnName()
	}
}

func (t tableColumn) LowercaseColumnName() string {
	return strings.ToLower(t.ColumnName)
}

func (t tableColumn) SwaggerColumnType() string {
	ct := t.MappedColumnType()

	if strings.Contains(t.ColumnType, "bit") || strings.Contains(t.ColumnType, "tinyint") {
		return "boolean"
	}
	if strings.Contains(ct, "int") {
		return "integer"
	}
	if strings.Contains(ct, "float") {
		return "number"
	}

	return "string"
}

func (t tableColumn) SwaggerFormat() string {
	ct := t.MappedColumnType()
	if strings.Contains(ct, "int") {
		return ct
	}

	if strings.Contains(ct, "float") {
		return "float"
	}
	if strings.Contains(ct, "byte") {
		return "byte"
	}

	if strings.Contains(ct, "time") {
		return "date-time"
	}
	if strings.Contains(ct, "date") {
		return "date"
	}

	if strings.Contains(t.ColumnName, "password") {
		return "password"
	}

	return ""
}

func (t tableColumn) MappedColumnType() string {
	switch t.ColumnType {
	case "blob", "tinyblob", "mediumblob", "longblob",
		"binary", "varbinary", "set", "enum":
		return "[]byte"
	case "text", "tinytext", "mediumtext", "longtext", "char", "varchar":
		return "string"
		//	case "tinyint":
		//		return "int8"
	case "utinyint":
		return "uint8"
	case "smallint":
		return "int16"
	case "usmallint":
		return "uint16"
	case "mediumint", "int":
		return "int32"
	case "umediumint", "uint":
		return "uint32"
	case "bigint":
		return "int64"
	case "ubigint":
		return "uint64"
	case "year":
		return "int16"
	case "time":
		return "time.Duration"
	case "date":
		return "mysql.Date"
	case "datetime", "timestamp":
		return "time.Time"
	case "float":
		return "float32"
	case "decimal", "double":
		return "float64"
	case "bit", "tinyint":
		return "bool"

	}

	return "interface{}"
}

func (t tableColumn) ColumnNullValue() string {
	switch t.ColumnType {
	case "blob", "tinyblob", "mediumblob", "longblob",
		"binary", "varbinary", "set", "enum":
		return "nil"
	case "char", "varchar", "text", "tinytext", "mediumtext", "longtext":
		return `""`
	case "utinyint", "smallint", "usmallint", "mediumint", "int", "umediumint", "uint", "bigint", "ubigint", "year", "float", "double", "decimal":
		return "0"
	case "time":
		return "nil"
	case "date":
		return "nil"
	case "datetime", "timestamp":
		return "nil"
	case "bit", "tinyint":
		return "false"
	}

	return "nil"
}

func (t tableColumn) NullCheck(varname string) string {
	switch t.ColumnType {
	case "blob", "tinyblob", "mediumblob", "longblob",
		"binary", "varbinary", "set", "enum":

		return fmt.Sprintf("%s != nil", varname)
	case "text", "tinytext", "mediumtext", "longtext",
		"char", "varchar":
		return fmt.Sprintf(`%s != ""`, varname)

	case "utinyint", "smallint", "usmallint", "mediumint", "int", "umediumint", "uint", "bigint", "ubigint", "year", "float", "decimal", "double", "time":
		return fmt.Sprintf("%s != 0", varname)
	case "date", "datetime", "timestamp":
		return fmt.Sprintf("!%s.IsZero()", varname)
	}

	return "false"

}

func colformat(cols []tableColumn, format string, joinstring string, str1, str2 func(tableColumn) string) string {
	result := []string{}
	for _, col := range cols {
		result = append(result, fmt.Sprintf(format, str1(col), str2(col)))
	}
	return strings.Join(result, joinstring)
}

func lcn(c tableColumn) string { return c.LowercaseColumnName() }
func mct(c tableColumn) string { return c.MappedColumnType() }

func capitalizedColumnNames(cols []tableColumn) []string {
	result := []string{}
	for _, c := range cols {
		result = append(result, c.CapitalizedColumnName())
	}
	return result
}

func columnNames(cols []tableColumn) []string {
	result := []string{}
	for _, c := range cols {
		result = append(result, c.ColumnName)
	}
	return result
}
<commit_msg>Add float type conversion to the possible thing you get back from a POST.<commit_after>package lib

import (
	"fmt"
	"strings"
)

type tableColumn struct {
	ColumnName string
	ColumnType string
	Primary    bool
	Nullable   bool
}

func (t tableColumn) CapitalizedColumnName() string {
	var result string
	for _, tp := range strings.Split(t.ColumnName, "_") {
		tp = strings.ToUpper(tp[0:1]) + strings.ToLower(tp[1:])
		result = result + tp
	}
	return result
}

func (t tableColumn) TextRightHandConvert() string {
	switch t.MappedColumnType() {
	case "int", "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64":
		return "i, _ := strconv.Atoi(form_" + t.LowercaseColumnName() + "); parsedField := " + t.MappedColumnType() + "(i)"
	case "float32":
		return "i, _ := strconv.ParseFloat(form_" + t.LowercaseColumnName() + ", 32); parseField := " + t.MappedColumnType() + "(i)"
	case "float64":
		return "i, _ := strconv.ParseFloat(form_" + t.LowercaseColumnName() + ", 64); parseField := " + t.MappedColumnType() + "(i)"
	case "[]byte":
		return "parsedField:= []byte(form_" + t.LowercaseColumnName() + ")"
	case "bool":
		return "parsedField, _ := strconv.ParseBool(form_" + t.LowercaseColumnName() + ")"
	default:
		return "parsedField:= form_" + t.LowercaseColumnName()
	}
}

func (t tableColumn) LowercaseColumnName() string {
	return strings.ToLower(t.ColumnName)
}

func (t tableColumn) SwaggerColumnType() string {
	ct := t.MappedColumnType()

	if strings.Contains(t.ColumnType, "bit") || strings.Contains(t.ColumnType, "tinyint") {
		return "boolean"
	}
	if strings.Contains(ct, "int") {
		return "integer"
	}
	if strings.Contains(ct, "float") {
		return "number"
	}

	return "string"
}

func (t tableColumn) SwaggerFormat() string {
	ct := t.MappedColumnType()
	if strings.Contains(ct, "int") {
		return ct
	}

	if strings.Contains(ct, "float") {
		return "float"
	}
	if strings.Contains(ct, "byte") {
		return "byte"
	}

	if strings.Contains(ct, "time") {
		return "date-time"
	}
	if strings.Contains(ct, "date") {
		return "date"
	}

	if strings.Contains(t.ColumnName, "password") {
		return "password"
	}

	return ""
}

func (t tableColumn) MappedColumnType() string {
	switch t.ColumnType {
	case "blob", "tinyblob", "mediumblob", "longblob",
		"binary", "varbinary", "set", "enum":
		return "[]byte"
	case "text", "tinytext", "mediumtext", "longtext", "char", "varchar":
		return "string"
		//	case "tinyint":
		//		return "int8"
	case "utinyint":
		return "uint8"
	case "smallint":
		return "int16"
	case "usmallint":
		return "uint16"
	case "mediumint", "int":
		return "int32"
	case "umediumint", "uint":
		return "uint32"
	case "bigint":
		return "int64"
	case "ubigint":
		return "uint64"
	case "year":
		return "int16"
	case "time":
		return "time.Duration"
	case "date":
		return "mysql.Date"
	case "datetime", "timestamp":
		return "time.Time"
	case "float":
		return "float32"
	case "decimal", "double":
		return "float64"
	case "bit", "tinyint":
		return "bool"

	}

	return "interface{}"
}

func (t tableColumn) ColumnNullValue() string {
	switch t.ColumnType {
	case "blob", "tinyblob", "mediumblob", "longblob",
		"binary", "varbinary", "set", "enum":
		return "nil"
	case "char", "varchar", "text", "tinytext", "mediumtext", "longtext":
		return `""`
	case "utinyint", "smallint", "usmallint", "mediumint", "int", "umediumint", "uint", "bigint", "ubigint", "year", "float", "double", "decimal":
		return "0"
	case "time":
		return "nil"
	case "date":
		return "nil"
	case "datetime", "timestamp":
		return "nil"
	case "bit", "tinyint":
		return "false"
	}

	return "nil"
}

func (t tableColumn) NullCheck(varname string) string {
	switch t.ColumnType {
	case "blob", "tinyblob", "mediumblob", "longblob",
		"binary", "varbinary", "set", "enum":

		return fmt.Sprintf("%s != nil", varname)
	case "text", "tinytext", "mediumtext", "longtext",
		"char", "varchar":
		return fmt.Sprintf(`%s != ""`, varname)

	case "utinyint", "smallint", "usmallint", "mediumint", "int", "umediumint", "uint", "bigint", "ubigint", "year", "float", "decimal", "double", "time":
		return fmt.Sprintf("%s != 0", varname)
	case "date", "datetime", "timestamp":
		return fmt.Sprintf("!%s.IsZero()", varname)
	}

	return "false"

}

func colformat(cols []tableColumn, format string, joinstring string, str1, str2 func(tableColumn) string) string {
	result := []string{}
	for _, col := range cols {
		result = append(result, fmt.Sprintf(format, str1(col), str2(col)))
	}
	return strings.Join(result, joinstring)
}

func lcn(c tableColumn) string { return c.LowercaseColumnName() }
func mct(c tableColumn) string { return c.MappedColumnType() }

func capitalizedColumnNames(cols []tableColumn) []string {
	result := []string{}
	for _, c := range cols {
		result = append(result, c.CapitalizedColumnName())
	}
	return result
}

func columnNames(cols []tableColumn) []string {
	result := []string{}
	for _, c := range cols {
		result = append(result, c.ColumnName)
	}
	return result
}
<|endoftext|>


<commit_before>package iamport

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"time"
)

// Client 아임포트 서버와 통신하는 클라이언트
type Client struct {
	APIKey      string
	APISecret   string
	AccessToken accessToken
	HTTP        *http.Client
}

type accessToken struct {
	Token   string
	Expired time.Time
}

// NewClient 아임포트로 부터 부여받은 API Key와 Api Secret을 사용하여 클라이언트를 새로 만든다.
func NewClient(apiKey string, apiSecret string, cli *http.Client) *Client {
	if cli == nil {
		cli = &http.Client{}
	}

	return &Client{
		APIKey:    apiKey,
		APISecret: apiSecret,
		HTTP:      cli,
	}
}

// GetToken APIKey와 APISecret을 사용하여 AccessToken을 받아 온다.
func (cli *Client) GetToken() error {
	if cli.APIKey == "" {
		return errors.New("iamport: APIKey is missing")
	}

	if cli.APISecret == "" {
		return errors.New("iamport: APISecret is missing")
	}

	form := url.Values{}
	form.Set("imp_key", cli.APIKey)
	form.Set("imp_secret", cli.APISecret)

	res, err := cli.HTTP.PostForm("https://api.iamport.kr/users/getToken", form)
	if err != nil {
		return err
	}

	if res.StatusCode == http.StatusUnauthorized {
		return errors.New("iamport: unauthorized")
	}

	if res.StatusCode != http.StatusOK {
		return errors.New("iamport: unknown error")
	}

	data := struct {
		Code     int    `json:"code"`
		Message  string `json:"message"`
		Response struct {
			AccessToken string `json:"access_token"`
			ExpiredAt   int64  `json:"expired_at"`
			Now         int64  `json:"now"`
		} `json:"response"`
	}{}

	err = json.NewDecoder(res.Body).Decode(&data)
	if err != nil {
		return err
	}

	if data.Code != 0 {
		return fmt.Errorf("iamport: %s", data.Message)
	}

	cli.AccessToken.Token = data.Response.AccessToken
	cli.AccessToken.Expired = time.Unix(data.Response.ExpiredAt, 0)

	return nil
}

// Payment 결제 정보
type Payment struct {
	ImpUID        string `json:"imp_uid"`
	MerchantUID   string `json:"merchant_uid"`
	PayMethod     string `json:"pay_method"`
	PGProvider    string `json:"pg_provider"`
	PGTID         string `json:"pg_tid"`
	ApplyNum      string `json:"apply_num"`
	CardName      string `json:"card_name"`
	CardQuota     int    `json:"card_quota"`
	VBankName     string `json:"vbank_name"`
	VBankNum      string `json:"vbank_num"`
	VBankHolder   string `json:"vbank_holder"`
	Name          string `json:"name"`
	Amount        int64  `json:"amount"`
	CancelAmount  string `json:"cancel_amount"`
	BuyerName     string `json:"buyer_name"`
	BuyerEmail    string `json:"buyer_email"`
	BuyerTel      string `json:"buyer_tel"`
	BuyerAddr     string `json:"buyer_addr"`
	BuyerPostCode string `json:"buyer_postcode"`
	CustomData    string `json:"custom_data"`
	UserAgent     string `json:"user_agent"`
	Status        string `json:"status"`
	PaidAt        int64  `json:"paid_at"`
	FailedAt      int64  `json:"failed_at"`
	CanceledAt    int64  `json:"canceled_at"`
	FailReason    string `json:"fail_reason"`
	CancelReason  string `json:"cancel_reason"`
	ReceiptURL    string `json:"receipt_url"`
}

func (cli *Client) authorization() (string, error) {
	now := time.Now()

	switch {
	case cli.AccessToken.Token == "",
		cli.AccessToken.Expired.IsZero(),
		!cli.AccessToken.Expired.Before(now):

		err := cli.GetToken()
		if err != nil {
			return "", err
		}
	}

	return cli.AccessToken.Token, nil
}

// GetPaymentImpUID imp_uid로 결제 정보 가져오기
//
// GET /payments/{imp_uid}
func (cli *Client) GetPaymentImpUID(iuid string) (Payment, error) {
	data := struct {
		Code     int     `json:"code"`
		Message  string  `json:"string"`
		Response Payment `json:"response"`
	}{}

	req, err := http.NewRequest("GET", fmt.Sprintf("https://api.iamport.kr/payments/%s", iuid), nil)
	if err != nil {
		return data.Response, err
	}

	auth, err := cli.authorization()
	if err != nil {
		return data.Response, err
	}
	req.Header.Set("Authorization", auth)

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

	if res.StatusCode == http.StatusUnauthorized {
		return data.Response, errors.New("iamport: unauthorized")
	}

	if res.StatusCode == http.StatusNotFound {
		return data.Response, errors.New("iamport: invalid imp_uid")
	}

	if res.StatusCode != http.StatusOK {
		return data.Response, errors.New("iamport: unknown error")
	}

	err = json.NewDecoder(res.Body).Decode(&data)
	if err != nil {
		return data.Response, err
	}

	if data.Code != 0 {
		return data.Response, fmt.Errorf("iamport: %s", data.Message)
	}

	return data.Response, nil
}
<commit_msg>merchant_uid로 결제 정보 가져오기<commit_after>package iamport

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"time"
)

// Client 아임포트 서버와 통신하는 클라이언트
type Client struct {
	APIKey      string
	APISecret   string
	AccessToken accessToken
	HTTP        *http.Client
}

type accessToken struct {
	Token   string
	Expired time.Time
}

// NewClient 아임포트로 부터 부여받은 API Key와 Api Secret을 사용하여 클라이언트를 새로 만든다.
func NewClient(apiKey string, apiSecret string, cli *http.Client) *Client {
	if cli == nil {
		cli = &http.Client{}
	}

	return &Client{
		APIKey:    apiKey,
		APISecret: apiSecret,
		HTTP:      cli,
	}
}

// GetToken APIKey와 APISecret을 사용하여 AccessToken을 받아 온다.
func (cli *Client) GetToken() error {
	if cli.APIKey == "" {
		return errors.New("iamport: APIKey is missing")
	}

	if cli.APISecret == "" {
		return errors.New("iamport: APISecret is missing")
	}

	form := url.Values{}
	form.Set("imp_key", cli.APIKey)
	form.Set("imp_secret", cli.APISecret)

	res, err := cli.HTTP.PostForm("https://api.iamport.kr/users/getToken", form)
	if err != nil {
		return err
	}

	if res.StatusCode == http.StatusUnauthorized {
		return errors.New("iamport: unauthorized")
	}

	if res.StatusCode != http.StatusOK {
		return errors.New("iamport: unknown error")
	}

	data := struct {
		Code     int    `json:"code"`
		Message  string `json:"message"`
		Response struct {
			AccessToken string `json:"access_token"`
			ExpiredAt   int64  `json:"expired_at"`
			Now         int64  `json:"now"`
		} `json:"response"`
	}{}

	err = json.NewDecoder(res.Body).Decode(&data)
	if err != nil {
		return err
	}

	if data.Code != 0 {
		return fmt.Errorf("iamport: %s", data.Message)
	}

	cli.AccessToken.Token = data.Response.AccessToken
	cli.AccessToken.Expired = time.Unix(data.Response.ExpiredAt, 0)

	return nil
}

// Payment 결제 정보
type Payment struct {
	ImpUID        string `json:"imp_uid"`
	MerchantUID   string `json:"merchant_uid"`
	PayMethod     string `json:"pay_method"`
	PGProvider    string `json:"pg_provider"`
	PGTID         string `json:"pg_tid"`
	ApplyNum      string `json:"apply_num"`
	CardName      string `json:"card_name"`
	CardQuota     int    `json:"card_quota"`
	VBankName     string `json:"vbank_name"`
	VBankNum      string `json:"vbank_num"`
	VBankHolder   string `json:"vbank_holder"`
	Name          string `json:"name"`
	Amount        int64  `json:"amount"`
	CancelAmount  string `json:"cancel_amount"`
	BuyerName     string `json:"buyer_name"`
	BuyerEmail    string `json:"buyer_email"`
	BuyerTel      string `json:"buyer_tel"`
	BuyerAddr     string `json:"buyer_addr"`
	BuyerPostCode string `json:"buyer_postcode"`
	CustomData    string `json:"custom_data"`
	UserAgent     string `json:"user_agent"`
	Status        string `json:"status"`
	PaidAt        int64  `json:"paid_at"`
	FailedAt      int64  `json:"failed_at"`
	CanceledAt    int64  `json:"canceled_at"`
	FailReason    string `json:"fail_reason"`
	CancelReason  string `json:"cancel_reason"`
	ReceiptURL    string `json:"receipt_url"`
}

func (cli *Client) authorization() (string, error) {
	now := time.Now()

	switch {
	case cli.AccessToken.Token == "",
		cli.AccessToken.Expired.IsZero(),
		!cli.AccessToken.Expired.Before(now):

		err := cli.GetToken()
		if err != nil {
			return "", err
		}
	}

	return cli.AccessToken.Token, nil
}

// GetPaymentImpUID imp_uid로 결제 정보 가져오기
//
// GET /payments/{imp_uid}
func (cli *Client) GetPaymentImpUID(iuid string) (Payment, error) {
	data := struct {
		Code     int     `json:"code"`
		Message  string  `json:"string"`
		Response Payment `json:"response"`
	}{}

	req, err := http.NewRequest("GET", fmt.Sprintf("https://api.iamport.kr/payments/%s", iuid), nil)
	if err != nil {
		return data.Response, err
	}

	auth, err := cli.authorization()
	if err != nil {
		return data.Response, err
	}
	req.Header.Set("Authorization", auth)

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

	if res.StatusCode == http.StatusUnauthorized {
		return data.Response, errors.New("iamport: unauthorized")
	}

	if res.StatusCode == http.StatusNotFound {
		return data.Response, errors.New("iamport: invalid imp_uid")
	}

	if res.StatusCode != http.StatusOK {
		return data.Response, errors.New("iamport: unknown error")
	}

	err = json.NewDecoder(res.Body).Decode(&data)
	if err != nil {
		return data.Response, err
	}

	if data.Code != 0 {
		return data.Response, fmt.Errorf("iamport: %s", data.Message)
	}

	return data.Response, nil
}

// GetPaymentMerchantUID merchant_uid로 결제 정보 가져오기
//
// GET /payments/find/{merchant_uid}
func (cli *Client) GetPaymentMerchantUID(muid string) (Payment, error) {
	data := struct {
		Code     int     `json:"code"`
		Message  string  `json:"string"`
		Response Payment `json:"response"`
	}{}

	req, err := http.NewRequest("GET",
		fmt.Sprintf("https://api.iamport.kr/payments/find/%s", muid), nil)
	if err != nil {
		return data.Response, err
	}

	auth, err := cli.authorization()
	if err != nil {
		return data.Response, err
	}
	req.Header.Set("Authorization", auth)

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

	if res.StatusCode == http.StatusUnauthorized {
		return data.Response, errors.New("iamport: unauthorized")
	}

	if res.StatusCode == http.StatusNotFound {
		return data.Response, errors.New("iamport: invalid merchant_uid")
	}

	if res.StatusCode != http.StatusOK {
		return data.Response, errors.New("iamport: unknown error")
	}

	err = json.NewDecoder(res.Body).Decode(&data)
	if err != nil {
		return data.Response, err
	}

	if data.Code != 0 {
		return data.Response, fmt.Errorf("iamport: %s", data.Message)
	}

	return data.Response, nil
}
<|endoftext|>