<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"errors"
	"io/ioutil"
	"net/http"
	"strings"
)

type Payload struct {
	SourceAddr string
	Body       []byte
	WaitCh     chan bool
}

type HttpServer struct {
	Config         *IssConfig
	Metrics        *Metrics
	Outlet         chan Payload
	ShutdownCh     chan int
	isShuttingDown bool
}

func NewHttpServer(config *IssConfig, outlet chan Payload, metrics *Metrics) *HttpServer {
	return &HttpServer{config, metrics, outlet, make(chan int), false}
}

func (s *HttpServer) Run() error {
	go s.awaitShutdown()

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		if s.isShuttingDown {
			http.Error(w, "Shutting down", 503)
			return
		}

		// check outlet depth?
		Logf("measure.http.health.get=1")
	})

	http.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
		if s.isShuttingDown {
			http.Error(w, "Shutting down", 503)
			return
		}

		if r.Method != "POST" {
			http.Error(w, "Only POST is accepted", 400)
			return
		}
		if r.Header.Get("Content-Type") != "application/logplex-1" {
			http.Error(w, "Only Content-Type application/logplex-1 is accepted", 400)
			return
		}

		if err := s.checkAuth(r); err != nil {
			http.Error(w, err.Error(), 401)
			return
		}

		b, err := ioutil.ReadAll(r.Body)
		r.Body.Close()
		if err != nil {
			http.Error(w, "Invalid Request", 400)
			return
		}

		remoteAddr := r.Header.Get("X-Forwarded-For")
		if remoteAddr == "" {
			remoteAddrParts := strings.Split(r.RemoteAddr, ":")
			remoteAddr = strings.Join(remoteAddrParts[:len(remoteAddrParts)-1], ":")
		}

		s.Metrics.Inbox <- NewCount("http.logs.post", 1)
		s.sendAndWait(remoteAddr, b)
	})

	if err := http.ListenAndServe(":"+s.Config.HttpPort, nil); err != nil {
		return err
	}

	return nil
}

func (s *HttpServer) awaitShutdown() {
	<-s.ShutdownCh
	Logf("ns=http at=shutdown")
	s.isShuttingDown = true
}

func (s *HttpServer) checkAuth(r *http.Request) error {
	header := r.Header.Get("Authorization")
	if header == "" {
		return errors.New("Authorization required")
	}
	headerParts := strings.SplitN(header, " ", 2)
	if len(headerParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	method := headerParts[0]
	if method != "Basic" {
		return errors.New("Only Basic Authorization is accepted")
	}

	encodedUserPass := headerParts[1]
	decodedUserPass, err := base64.StdEncoding.DecodeString(encodedUserPass)
	if err != nil {
		return errors.New("Authorization header is malformed")
	}

	userPassParts := bytes.SplitN(decodedUserPass, []byte{':'}, 2)
	if len(userPassParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	user := userPassParts[0]
	pass := userPassParts[1]
	token, ok := s.Config.Tokens[string(user)]
	if !ok {
		return errors.New("Unknown user")
	}
	if token != string(pass) {
		return errors.New("Incorrect token")
	}

	return nil
}

func (s *HttpServer) sendAndWait(remoteAddr string, b []byte) {
	waitCh := make(chan bool)
	s.Outlet <- Payload{remoteAddr, b, waitCh}
	<-waitCh
}
<commit_msg>Give messages 5 seconds to be delivered.<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"errors"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

type Payload struct {
	SourceAddr string
	Body       []byte
	WaitCh     chan bool
}

type HttpServer struct {
	Config         *IssConfig
	Metrics        *Metrics
	Outlet         chan Payload
	ShutdownCh     chan int
	isShuttingDown bool
}

func NewHttpServer(config *IssConfig, outlet chan Payload, metrics *Metrics) *HttpServer {
	return &HttpServer{config, metrics, outlet, make(chan int), false}
}

func (s *HttpServer) Run() error {
	go s.awaitShutdown()

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		if s.isShuttingDown {
			http.Error(w, "Shutting down", 503)
			return
		}

		// check outlet depth?
		Logf("measure.http.health.get=1")
	})

	http.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
		if s.isShuttingDown {
			http.Error(w, "Shutting down", 503)
			return
		}

		if r.Method != "POST" {
			http.Error(w, "Only POST is accepted", 400)
			return
		}
		if r.Header.Get("Content-Type") != "application/logplex-1" {
			http.Error(w, "Only Content-Type application/logplex-1 is accepted", 400)
			return
		}

		if err := s.checkAuth(r); err != nil {
			http.Error(w, err.Error(), 401)
			return
		}

		b, err := ioutil.ReadAll(r.Body)
		r.Body.Close()
		if err != nil {
			http.Error(w, "Invalid Request", 400)
			return
		}

		remoteAddr := r.Header.Get("X-Forwarded-For")
		if remoteAddr == "" {
			remoteAddrParts := strings.Split(r.RemoteAddr, ":")
			remoteAddr = strings.Join(remoteAddrParts[:len(remoteAddrParts)-1], ":")
		}

		if err := s.sendAndWait(remoteAddr, b); err != nil {
			http.Error(w, "Problem delivering messages", 500)
			Logf("measure.http.logs.post.error=1 message=%q", err)
			return
		}

		s.Metrics.Inbox <- NewCount("http.logs.post.success", 1)
	})

	if err := http.ListenAndServe(":"+s.Config.HttpPort, nil); err != nil {
		return err
	}

	return nil
}

func (s *HttpServer) awaitShutdown() {
	<-s.ShutdownCh
	Logf("ns=http at=shutdown")
	s.isShuttingDown = true
}

func (s *HttpServer) checkAuth(r *http.Request) error {
	header := r.Header.Get("Authorization")
	if header == "" {
		return errors.New("Authorization required")
	}
	headerParts := strings.SplitN(header, " ", 2)
	if len(headerParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	method := headerParts[0]
	if method != "Basic" {
		return errors.New("Only Basic Authorization is accepted")
	}

	encodedUserPass := headerParts[1]
	decodedUserPass, err := base64.StdEncoding.DecodeString(encodedUserPass)
	if err != nil {
		return errors.New("Authorization header is malformed")
	}

	userPassParts := bytes.SplitN(decodedUserPass, []byte{':'}, 2)
	if len(userPassParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	user := userPassParts[0]
	pass := userPassParts[1]
	token, ok := s.Config.Tokens[string(user)]
	if !ok {
		return errors.New("Unknown user")
	}
	if token != string(pass) {
		return errors.New("Incorrect token")
	}

	return nil
}

func (s *HttpServer) sendAndWait(remoteAddr string, b []byte) error {
	waitCh := make(chan bool)
	deadlineCh := time.After(time.Duration(5) * time.Second)
	s.Outlet <- Payload{remoteAddr, b, waitCh}
	select {
	case <-waitCh:
		return nil
	case <-deadlineCh:
		return errors.New("Delivery timed out")
	}
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"
	"net/http"
	"net/http/httputil"
)

const (
	ContentTypeJson string = "application/json"
	ContentTypeHtml        = "text/html"
)

func LogRequest(r *http.Request, useBody bool) {
	buff, _ := httputil.DumpRequest(r, useBody)
	log.Debug("Request: " + string(buff))
}

func LogResponse(r *http.Response, useBody bool) {
	buff, _ := httputil.DumpResponse(r, useBody)
	log.Debug("Response: " + string(buff))
}

// HTTP のステータスコードを付加したエラー。
type HttpStatusError struct {
	status int
	msg    string

	cause error
}

func NewHttpStatusError(status int, msg string, cause error) error {
	return &HttpStatusError{status, msg, cause}
}

func (err *HttpStatusError) Error() string {
	buff := err.msg
	if err.cause != nil {
		buff += fmt.Sprintln()
		buff += "caused by: "
		buff += err.cause.Error()
	}
	return buff
}

func (err *HttpStatusError) Status() int {
	return err.status
}

func (err *HttpStatusError) Message() string {
	return err.msg
}
<commit_msg>HTTP サーバー用エラーラッパーに原因を取得するメソッドを追加<commit_after>package util

import (
	"fmt"
	"net/http"
	"net/http/httputil"
)

const (
	ContentTypeJson string = "application/json"
	ContentTypeHtml        = "text/html"
)

func LogRequest(r *http.Request, useBody bool) {
	buff, _ := httputil.DumpRequest(r, useBody)
	log.Debug("Request: " + string(buff))
}

func LogResponse(r *http.Response, useBody bool) {
	buff, _ := httputil.DumpResponse(r, useBody)
	log.Debug("Response: " + string(buff))
}

// HTTP のステータスコードを付加したエラー。
type HttpStatusError struct {
	status int
	msg    string

	cause error
}

func NewHttpStatusError(status int, msg string, cause error) error {
	return &HttpStatusError{status, msg, cause}
}

func (err *HttpStatusError) Error() string {
	buff := err.msg
	if err.cause != nil {
		buff += fmt.Sprintln()
		buff += "caused by: "
		buff += err.cause.Error()
	}
	return buff
}

func (err *HttpStatusError) Status() int {
	return err.status
}

func (err *HttpStatusError) Message() string {
	return err.msg
}

func (err *HttpStatusError) Cause() error {
	return err.cause
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)

func (fw *Flywheel) SendPing(op string) Pong {
	replyTo := make(chan Pong, 1)
	sreq := Ping{replyTo: replyTo}
	switch op {
	case "start":
		sreq.requestStart = true
	case "stop":
		sreq.requestStop = true
	case "status":
		sreq.noop = true
	}

	fw.pings <- sreq

	status := <-replyTo
	return status
}

func (fw *Flywheel) ProxyEndpoint(hostname string) string {
	vhost, ok := fw.config.Vhosts[hostname]
	if ok {
		return vhost
	}
	return fw.config.Endpoint
}

func (fw *Flywheel) Proxy(w http.ResponseWriter, r *http.Request) {
	client := &http.Client{}
	r.URL.Query().Del("flywheel")

	endpoint := fw.ProxyEndpoint(r.Host)
	if endpoint == "" {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Invalid flywheel endpoint config"))
		log.Fatal("Invalid endpoint URL")
	}

	r.URL.Scheme = "http"

	r.URL.Host = endpoint
	r.RequestURI = ""
	resp, err := client.Do(r)
	if err != nil {
		log.Print(err)
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	for key, value := range resp.Header {
		w.Header()[key] = value
	}
	w.WriteHeader(resp.StatusCode)

	_, err = io.Copy(w, resp.Body)
	if err != nil {
		log.Print(err)
	}
}

func (fw *Flywheel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.RequestURI)

	query := r.URL.Query()
	flywheel := query.Get("flywheel")

	if flywheel == "config" {
		buf, err := json.Marshal(fw.config) // Might be unsafe, but this should be read only.
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, err)
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.Write(buf)
		}
		return
	}

	pong := fw.SendPing(query.Get("flywheel"))

	if flywheel == "start" {
		query.Del("flywheel")
		r.URL.RawQuery = query.Encode()
		w.Header().Set("Location", r.URL.String())
		w.WriteHeader(http.StatusTemporaryRedirect)
		return
	}

	accept := query.Get("Accept")
	var acceptHtml bool
	if accept != "" {
		htmlIndex := strings.Index(accept, "text/html")
		jsonIndex := strings.Index(accept, "application/json")
		if htmlIndex != -1 {
			acceptHtml = jsonIndex == -1 || htmlIndex < jsonIndex
		}
	}

	if flywheel != "" {
		buf, err := json.Marshal(pong)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, err)
		} else if flywheel != "status" {
			query.Del("flywheel")
			r.URL.RawQuery = query.Encode()
			w.Header().Set("Content-Type", "application/json")
			if acceptHtml {
				w.Header().Set("Location", r.URL.String())
				w.WriteHeader(http.StatusTemporaryRedirect)
			}
			w.Write(buf)
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.Write(buf)
		}
		return
	}

	if pong.Err != nil {
		body := fmt.Sprintf(HTML_ERROR, pong.Err)
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(body))
		return
	}

	switch pong.Status {
	case STOPPED:
		query.Set("flywheel", "start")
		r.URL.RawQuery = query.Encode()
		body := fmt.Sprintf(HTML_STOPPED, r.URL)
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(body))
	case STARTING:
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(HTML_STARTING))
	case STARTED:
		fw.Proxy(w, r)
	case STOPPING:
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(HTML_STOPPING))
	case UNHEALTHY:
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(HTML_UNHEALTHY))
	}
}
<commit_msg>Missing import<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
)

func (fw *Flywheel) SendPing(op string) Pong {
	replyTo := make(chan Pong, 1)
	sreq := Ping{replyTo: replyTo}
	switch op {
	case "start":
		sreq.requestStart = true
	case "stop":
		sreq.requestStop = true
	case "status":
		sreq.noop = true
	}

	fw.pings <- sreq

	status := <-replyTo
	return status
}

func (fw *Flywheel) ProxyEndpoint(hostname string) string {
	vhost, ok := fw.config.Vhosts[hostname]
	if ok {
		return vhost
	}
	return fw.config.Endpoint
}

func (fw *Flywheel) Proxy(w http.ResponseWriter, r *http.Request) {
	client := &http.Client{}
	r.URL.Query().Del("flywheel")

	endpoint := fw.ProxyEndpoint(r.Host)
	if endpoint == "" {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Invalid flywheel endpoint config"))
		log.Fatal("Invalid endpoint URL")
	}

	r.URL.Scheme = "http"

	r.URL.Host = endpoint
	r.RequestURI = ""
	resp, err := client.Do(r)
	if err != nil {
		log.Print(err)
		w.WriteHeader(http.StatusServiceUnavailable)
		return
	}

	for key, value := range resp.Header {
		w.Header()[key] = value
	}
	w.WriteHeader(resp.StatusCode)

	_, err = io.Copy(w, resp.Body)
	if err != nil {
		log.Print(err)
	}
}

func (fw *Flywheel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("[%s] %s %s", r.RemoteAddr, r.Method, r.RequestURI)

	query := r.URL.Query()
	flywheel := query.Get("flywheel")

	if flywheel == "config" {
		buf, err := json.Marshal(fw.config) // Might be unsafe, but this should be read only.
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, err)
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.Write(buf)
		}
		return
	}

	pong := fw.SendPing(query.Get("flywheel"))

	if flywheel == "start" {
		query.Del("flywheel")
		r.URL.RawQuery = query.Encode()
		w.Header().Set("Location", r.URL.String())
		w.WriteHeader(http.StatusTemporaryRedirect)
		return
	}

	accept := query.Get("Accept")
	var acceptHtml bool
	if accept != "" {
		htmlIndex := strings.Index(accept, "text/html")
		jsonIndex := strings.Index(accept, "application/json")
		if htmlIndex != -1 {
			acceptHtml = jsonIndex == -1 || htmlIndex < jsonIndex
		}
	}

	if flywheel != "" {
		buf, err := json.Marshal(pong)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, err)
		} else if flywheel != "status" {
			query.Del("flywheel")
			r.URL.RawQuery = query.Encode()
			w.Header().Set("Content-Type", "application/json")
			if acceptHtml {
				w.Header().Set("Location", r.URL.String())
				w.WriteHeader(http.StatusTemporaryRedirect)
			}
			w.Write(buf)
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.Write(buf)
		}
		return
	}

	if pong.Err != nil {
		body := fmt.Sprintf(HTML_ERROR, pong.Err)
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(body))
		return
	}

	switch pong.Status {
	case STOPPED:
		query.Set("flywheel", "start")
		r.URL.RawQuery = query.Encode()
		body := fmt.Sprintf(HTML_STOPPED, r.URL)
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(body))
	case STARTING:
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(HTML_STARTING))
	case STARTED:
		fw.Proxy(w, r)
	case STOPPING:
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(HTML_STOPPING))
	case UNHEALTHY:
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(HTML_UNHEALTHY))
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package utils

import (
	"crypto/tls"
	"net/http"
	"sync"
)

var insecureClient = (*http.Client)(nil)
var insecureClientMutex = sync.Mutex{}

func GetNonValidatingHTTPClient() *http.Client {
	insecureClientMutex.Lock()
	defer insecureClientMutex.Unlock()
	if insecureClient == nil {
		insecureConfig := &tls.Config{InsecureSkipVerify: true}
		insecureTransport := NewHttpTLSTransport(insecureConfig)
		insecureClient = &http.Client{Transport: insecureTransport}
	}
	return insecureClient
}

// NewHttpTLSTransport returns a new http.Transport constructed with the TLS config
// and the necessary parameters for Juju.
func NewHttpTLSTransport(tlsConfig *tls.Config) *http.Transport {
	// See https://code.google.com/p/go/issues/detail?id=4677
	// We need to force the connection to close each time so that we don't
	// hit the above Go bug.
	return &http.Transport{
		TLSClientConfig:   tlsConfig,
		DisableKeepAlives: true,
	}
}

// NewHttpTransport returns a new http.Transport constructed with the necessary
// parameters for Juju.
func NewHttpTransport() *http.Transport {
	// See https://code.google.com/p/go/issues/detail?id=4677
	// We need to force the connection to close each time so that we don't
	// hit the above Go bug.
	return &http.Transport{
		Proxy:             http.ProxyFromEnvironment,
		DisableKeepAlives: true,
	}
}
<commit_msg>Initial refactoring<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package utils

import (
	"crypto/tls"
	"net/http"
	"sync"
)

var clientMutex = sync.Mutex{}
var insecureClient = (*http.Client)(nil)
var secureClient = (*http.Client)(nil)

func init() {
	// We expect GetHTTPClient() to be used normally but in case
	// calls http.Get() are used we set up the transport here too.
	// See https://code.google.com/p/go/issues/detail?id=4677
	// We need to force the connection to close each time so that we don't
	// hit the above Go bug.
	defaultTransport := http.DefaultTransport.(*http.Transport)
	defaultTransport.DisableKeepAlives = true
	//	registerFileProtocol(defaultTransport)
}

// registerFileProtocol registers support for file:// URLs on the
// given transport.
func registerFileProtocol(transport *http.Transport) {
	transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
}

// SSLHostnameVerification is used as a switch for when a given provider might
// use self-signed credentials and we should not try to verify the hostname on
// the TLS/SSL certificates
type SSLHostnameVerification bool

const (
	// VerifySSLHostnames ensures we verify the hostname on the certificate
	// matches the host we are connecting and is signed
	VerifySSLHostnames = SSLHostnameVerification(true)
	// NoVerifySSLHostnames informs us to skip verifying the hostname
	// matches a valid certificate
	NoVerifySSLHostnames = SSLHostnameVerification(false)
)

// GetHTTPClient returns either a standard http client or
// non validating client depending on the value of verify.
func GetHTTPClient(verify SSLHostnameVerification) *http.Client {
	if verify == VerifySSLHostnames {
		return GetValidatingHTTPClient()
	}
	return GetNonValidatingHTTPClient()
}

func GetValidatingHTTPClient() *http.Client {
	logger.Infof("hostname SSL verification enabled")
	clientMutex.Lock()
	defer clientMutex.Unlock()
	if secureClient == nil {
		secureTransport := NewHttpTransport()
		secureClient = &http.Client{Transport: secureTransport}
	}
	return secureClient
}

func GetNonValidatingHTTPClient() *http.Client {
	logger.Infof("hostname SSL verification disabled")
	clientMutex.Lock()
	defer clientMutex.Unlock()
	if insecureClient == nil {
		insecureConfig := &tls.Config{InsecureSkipVerify: true}
		insecureTransport := NewHttpTLSTransport(insecureConfig)
		insecureClient = &http.Client{Transport: insecureTransport}
	}
	return insecureClient
}

// NewHttpTLSTransport returns a new http.Transport constructed with the TLS config
// and the necessary parameters for Juju.
func NewHttpTLSTransport(tlsConfig *tls.Config) *http.Transport {
	// See https://code.google.com/p/go/issues/detail?id=4677
	// We need to force the connection to close each time so that we don't
	// hit the above Go bug.
	transport := &http.Transport{
		TLSClientConfig:   tlsConfig,
		DisableKeepAlives: true,
	}
	registerFileProtocol(transport)
	return transport
}

// NewHttpTransport returns a new http.Transport constructed with the necessary
// parameters for Juju.
func NewHttpTransport() *http.Transport {
	// See https://code.google.com/p/go/issues/detail?id=4677
	// We need to force the connection to close each time so that we don't
	// hit the above Go bug.
	transport := &http.Transport{
		Proxy:             http.ProxyFromEnvironment,
		DisableKeepAlives: true,
	}
	registerFileProtocol(transport)
	return transport
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"

	"github.com/naoina/denco"
	"github.com/pocke/gfm-viewer/env"
	"github.com/pocke/hlog.go"
	"github.com/yosssi/ace"
)

type Server struct {
	storage *Storage
}

func NewServer() *Server {
	s := &Server{
		storage: NewStorage(),
	}

	go func() {
		mux := denco.NewMux()
		f, err := mux.Build([]denco.Handler{
			mux.GET("/", s.indexHandler),
			mux.POST("/auth", s.authHandler),
			mux.GET("/files/*path", s.ServeFile),
			mux.GET("/css/github-markdown.css", s.serveCSS),
		})
		if err != nil {
			panic(err)
		}
		handler := f.ServeHTTP
		if env.DEBUG {
			handler = hlog.Wrap(f.ServeHTTP)
		}
		http.HandleFunc("/", handler)
		// TODO: port
		http.ListenAndServe(":1124", nil)
	}()

	return s
}

func (s *Server) ServeFile(w http.ResponseWriter, r *http.Request, p denco.Params) {
	path := p.Get("path")
	html, ok := s.storage.Get(path)
	if !ok {
		http.Error(w, fmt.Sprintf("%s page not found", path), http.StatusNotFound)
		return
	}
	w.Write([]byte(html))
}

func (s *Server) authHandler(w http.ResponseWriter, r *http.Request, _ denco.Params) {
	r.ParseForm()
	v := r.PostForm
	user := v.Get("username")
	pass := v.Get("password")

	err := s.storage.token.Init(user, pass)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	s.storage.UpdateAll()
	http.Redirect(w, r, "/", http.StatusFound)
}

func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request, _ denco.Params) {
	if s.storage.token.hasToken() {

		loadAce(w, "index", s.storage.Index())
	} else {

		loadAce(w, "before_auth", nil)
	}
}

func loadAce(w http.ResponseWriter, action string, data interface{}) {
	tpl, err := ace.Load("assets/"+action, "", &ace.Options{
		DynamicReload: env.DEBUG,
		Asset:         Asset,
	})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = tpl.Execute(w, data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (s *Server) serveCSS(w http.ResponseWriter, r *http.Request, _ denco.Params) {
	file, err := Asset("assets/github-markdown.css")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/css")
	w.Write(file)
}
<commit_msg>Remove empty line.<commit_after>package main

import (
	"fmt"
	"net/http"

	"github.com/naoina/denco"
	"github.com/pocke/gfm-viewer/env"
	"github.com/pocke/hlog.go"
	"github.com/yosssi/ace"
)

type Server struct {
	storage *Storage
}

func NewServer() *Server {
	s := &Server{
		storage: NewStorage(),
	}

	go func() {
		mux := denco.NewMux()
		f, err := mux.Build([]denco.Handler{
			mux.GET("/", s.indexHandler),
			mux.POST("/auth", s.authHandler),
			mux.GET("/files/*path", s.ServeFile),
			mux.GET("/css/github-markdown.css", s.serveCSS),
		})
		if err != nil {
			panic(err)
		}
		handler := f.ServeHTTP
		if env.DEBUG {
			handler = hlog.Wrap(f.ServeHTTP)
		}
		http.HandleFunc("/", handler)
		// TODO: port
		http.ListenAndServe(":1124", nil)
	}()

	return s
}

func (s *Server) ServeFile(w http.ResponseWriter, r *http.Request, p denco.Params) {
	path := p.Get("path")
	html, ok := s.storage.Get(path)
	if !ok {
		http.Error(w, fmt.Sprintf("%s page not found", path), http.StatusNotFound)
		return
	}
	w.Write([]byte(html))
}

func (s *Server) authHandler(w http.ResponseWriter, r *http.Request, _ denco.Params) {
	r.ParseForm()
	v := r.PostForm
	user := v.Get("username")
	pass := v.Get("password")

	err := s.storage.token.Init(user, pass)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	s.storage.UpdateAll()
	http.Redirect(w, r, "/", http.StatusFound)
}

func (s *Server) indexHandler(w http.ResponseWriter, r *http.Request, _ denco.Params) {
	if s.storage.token.hasToken() {
		loadAce(w, "index", s.storage.Index())
	} else {
		loadAce(w, "before_auth", nil)
	}
}

func loadAce(w http.ResponseWriter, action string, data interface{}) {
	tpl, err := ace.Load("assets/"+action, "", &ace.Options{
		DynamicReload: env.DEBUG,
		Asset:         Asset,
	})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = tpl.Execute(w, data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (s *Server) serveCSS(w http.ResponseWriter, r *http.Request, _ denco.Params) {
	file, err := Asset("assets/github-markdown.css")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/css")
	w.Write(file)
}
<|endoftext|>


<commit_before>package yiigo

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

// defaultHTTPTimeout default http request timeout
const defaultHTTPTimeout = 10 * time.Second

// errCookieFileNotFound cookie file not found error
var errCookieFileNotFound = errors.New("cookie file not found")

// httpClientOptions http client options
type httpClientOptions struct {
	dialTimeout           time.Duration
	dialKeepAlive         time.Duration
	fallbackDelay         time.Duration
	maxIdleConns          int
	maxIdleConnsPerHost   int
	maxConnsPerHost       int
	idleConnTimeout       time.Duration
	tlsConfig             *tls.Config
	tlsHandshakeTimeout   time.Duration
	expectContinueTimeout time.Duration
}

// HTTPClientOption configures how we set up the http client
type HTTPClientOption interface {
	apply(options *httpClientOptions)
}

// funcHTTPClientOption implements http client option
type funcHTTPClientOption struct {
	f func(options *httpClientOptions)
}

func (fo *funcHTTPClientOption) apply(o *httpClientOptions) {
	fo.f(o)
}

func newFuncHTTPOption(f func(options *httpClientOptions)) *funcHTTPClientOption {
	return &funcHTTPClientOption{f: f}
}

// WithHTTPDialTimeout specifies the `DialTimeout` to net.Dialer.
func WithHTTPDialTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.dialTimeout = d
	})
}

// WithHTTPDialKeepAlive specifies the `KeepAlive` to net.Dialer.
func WithHTTPDialKeepAlive(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.dialKeepAlive = d
	})
}

// WithHTTPDialFallbackDelay specifies the `FallbackDelay` to net.Dialer.
func WithHTTPDialFallbackDelay(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.fallbackDelay = d
	})
}

// WithHTTPMaxIdleConns specifies the `MaxIdleConns` to http client.
func WithHTTPMaxIdleConns(n int) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.maxIdleConns = n
	})
}

// WithHTTPMaxIdleConnsPerHost specifies the `MaxIdleConnsPerHost` to http client.
func WithHTTPMaxIdleConnsPerHost(n int) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.maxIdleConnsPerHost = n
	})
}

// WithHTTPMaxConnsPerHost specifies the `MaxConnsPerHost` to http client.
func WithHTTPMaxConnsPerHost(n int) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.maxConnsPerHost = n
	})
}

// WithHTTPIdleConnTimeout specifies the `IdleConnTimeout` to http client.
func WithHTTPIdleConnTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.idleConnTimeout = d
	})
}

// WithHTTPSSLCertFile specifies the TLS with cert file to http client.
func WithHTTPTLSConfig(c *tls.Config) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.tlsConfig = c
	})
}

// WithHTTPTLSHandshakeTimeout specifies the `TLSHandshakeTimeout` to http client.
func WithHTTPTLSHandshakeTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.tlsHandshakeTimeout = d
	})
}

// WithHTTPExpectContinueTimeout specifies the `ExpectContinueTimeout` to http client.
func WithHTTPExpectContinueTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.expectContinueTimeout = d
	})
}

// httpRequestOptions http request options
type httpRequestOptions struct {
	headers          map[string]string
	cookieFile       string
	withCookies      bool
	cookieSave       bool
	cookieReplace    bool
	disableKeepAlive bool
	timeout          time.Duration
}

// HTTPRequestOption configures how we set up the http request
type HTTPRequestOption interface {
	apply(*httpRequestOptions) error
}

// funcHTTPRequestOption implements request option
type funcHTTPRequestOption struct {
	f func(*httpRequestOptions) error
}

func (fo *funcHTTPRequestOption) apply(r *httpRequestOptions) error {
	return fo.f(r)
}

func newFuncHTTPRequestOption(f func(*httpRequestOptions) error) *funcHTTPRequestOption {
	return &funcHTTPRequestOption{f: f}
}

// WithRequestHeader specifies the headers to http request.
func WithRequestHeader(key, value string) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.headers[key] = value

		return nil
	})
}

// WithRequestCookieFile specifies the file which to save http response cookies.
func WithRequestCookieFile(file string) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		path, err := filepath.Abs(file)

		if err != nil {
			return err
		}

		o.cookieFile = path

		return mkCookieFile(path)
	})
}

// WithRequestCookies specifies http requested with cookies.
func WithRequestCookies(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.withCookies = b

		return nil
	})
}

// WithRequestCookieSave specifies save the http response cookies.
func WithRequestCookieSave(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.cookieSave = b

		return nil
	})
}

// WithRequestCookieReplace specifies replace the old http response cookies.
func WithRequestCookieReplace(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.cookieReplace = b

		return nil
	})
}

// WithRequestDisableKeepAlive specifies close the connection after
// replying to this request (for servers) or after sending this
// request and reading its response (for clients).
func WithRequestDisableKeepAlive(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.disableKeepAlive = b

		return nil
	})
}

// WithRequestTimeout specifies the timeout to http request.
func WithRequestTimeout(d time.Duration) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.timeout = d

		return nil
	})
}

// HTTPClient http client
type HTTPClient struct {
	client *http.Client
}

// Get http get request
func (h *HTTPClient) Get(url string, options ...HTTPRequestOption) ([]byte, error) {
	req, err := http.NewRequest("GET", url, nil)

	if err != nil {
		return nil, err
	}

	o := &httpRequestOptions{
		headers: make(map[string]string),
		timeout: defaultHTTPTimeout,
	}

	if len(options) > 0 {
		for _, option := range options {
			if err := option.apply(o); err != nil {
				return nil, err
			}
		}
	}

	if len(o.headers) > 0 {
		for k, v := range o.headers {
			req.Header.Set(k, v)
		}
	}

	if o.withCookies {
		cookies, err := getCookies(o.cookieFile)

		if err != nil {
			return nil, err
		}

		for _, c := range cookies {
			req.AddCookie(c)
		}
	}

	if o.disableKeepAlive {
		req.Close = true
	}

	ctx, cancel := context.WithTimeout(context.TODO(), o.timeout)

	defer cancel()

	resp, err := h.client.Do(req.WithContext(ctx))

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if o.cookieSave {
		if err := saveCookie(resp.Cookies(), o.cookieFile, o.cookieReplace); err != nil {
			return nil, err
		}
	}

	if resp.StatusCode != http.StatusOK {
		io.Copy(ioutil.Discard, resp.Body)

		return nil, fmt.Errorf("error http code: %d", resp.StatusCode)
	}

	b, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return nil, err
	}

	return b, nil
}

// Post http post request
func (h *HTTPClient) Post(url string, body []byte, options ...HTTPRequestOption) ([]byte, error) {
	req, err := http.NewRequest("POST", url, bytes.NewReader(body))

	if err != nil {
		return nil, err
	}

	o := &httpRequestOptions{
		headers: make(map[string]string),
		timeout: defaultHTTPTimeout,
	}

	if len(options) > 0 {
		for _, option := range options {
			if err := option.apply(o); err != nil {
				return nil, err
			}
		}
	}

	if len(o.headers) > 0 {
		for k, v := range o.headers {
			req.Header.Set(k, v)
		}
	}

	if o.withCookies {
		cookies, err := getCookies(o.cookieFile)

		if err != nil {
			return nil, err
		}

		for _, c := range cookies {
			req.AddCookie(c)
		}
	}

	if o.disableKeepAlive {
		req.Close = true
	}

	ctx, cancel := context.WithTimeout(context.TODO(), o.timeout)

	defer cancel()

	resp, err := h.client.Do(req.WithContext(ctx))

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if o.cookieSave {
		if err := saveCookie(resp.Cookies(), o.cookieFile, o.cookieReplace); err != nil {
			return nil, err
		}
	}

	if resp.StatusCode != http.StatusOK {
		io.Copy(ioutil.Discard, resp.Body)

		return nil, fmt.Errorf("error http code: %d", resp.StatusCode)
	}

	b, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return nil, err
	}

	return b, nil
}

// defaultHTTPClient default http client
var defaultHTTPClient = &HTTPClient{
	client: &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 60 * time.Second,
			}).DialContext,
			MaxIdleConns:          0,
			MaxIdleConnsPerHost:   1000,
			MaxConnsPerHost:       1000,
			IdleConnTimeout:       60 * time.Second,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	},
}

// NewHTTPClient returns a new http client
func NewHTTPClient(options ...HTTPClientOption) *HTTPClient {
	o := &httpClientOptions{
		dialTimeout:           30 * time.Second,
		dialKeepAlive:         60 * time.Second,
		maxIdleConns:          0,
		maxIdleConnsPerHost:   1000,
		maxConnsPerHost:       1000,
		idleConnTimeout:       60 * time.Second,
		tlsHandshakeTimeout:   10 * time.Second,
		expectContinueTimeout: 1 * time.Second,
	}

	if len(options) > 0 {
		for _, option := range options {
			option.apply(o)
		}
	}

	t := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   o.dialTimeout,
			KeepAlive: o.dialKeepAlive,
		}).DialContext,
		MaxConnsPerHost:       o.maxConnsPerHost,
		MaxIdleConnsPerHost:   o.maxIdleConnsPerHost,
		MaxIdleConns:          o.maxIdleConns,
		IdleConnTimeout:       o.idleConnTimeout,
		TLSClientConfig:       o.tlsConfig,
		TLSHandshakeTimeout:   o.tlsHandshakeTimeout,
		ExpectContinueTimeout: o.expectContinueTimeout,
	}

	c := &HTTPClient{
		client: &http.Client{
			Transport: t,
		},
	}

	return c
}

// HTTPGet http get request
func HTTPGet(url string, options ...HTTPRequestOption) ([]byte, error) {
	return defaultHTTPClient.Get(url, options...)
}

// HTTPPost http post request
func HTTPPost(url string, body []byte, options ...HTTPRequestOption) ([]byte, error) {
	return defaultHTTPClient.Post(url, body, options...)
}

// mkCookieFile create cookie file
func mkCookieFile(path string) error {
	dir := filepath.Dir(path)

	// make dir if not exsit
	if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
			return err
		}
	}

	// create file if not exsit
	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
		if _, err := os.Create(path); err != nil {
			return err
		}
	}

	return nil
}

// getCookie get http saved cookies
func getCookies(cookieFile string) ([]*http.Cookie, error) {
	if cookieFile == "" {
		return nil, errCookieFileNotFound
	}

	cookieM := make(map[string]*http.Cookie)
	content, err := ioutil.ReadFile(cookieFile)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(content, &cookieM); err != nil {
		return nil, err
	}

	cookies := make([]*http.Cookie, 0, len(cookieM))

	for _, v := range cookieM {
		cookies = append(cookies, v)
	}

	return cookies, nil
}

// saveCookie save http cookies
func saveCookie(cookies []*http.Cookie, cookieFile string, replace bool) error {
	if len(cookies) == 0 {
		return nil
	}

	if cookieFile == "" {
		return errCookieFileNotFound
	}

	cookieM := make(map[string]*http.Cookie)

	if !replace {
		content, err := ioutil.ReadFile(cookieFile)

		if err != nil {
			return err
		}

		if len(content) > 0 {
			if err := json.Unmarshal(content, &cookieM); err != nil {
				return err
			}
		}
	}

	for _, c := range cookies {
		cookieM[c.Name] = c
	}

	b, err := json.Marshal(cookieM)

	if err != nil {
		return err
	}

	return ioutil.WriteFile(cookieFile, b, os.ModePerm)
}
<commit_msg>update http<commit_after>package yiigo

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

// defaultHTTPTimeout default http request timeout
const defaultHTTPTimeout = 10 * time.Second

// errCookieFileNotFound cookie file not found error
var errCookieFileNotFound = errors.New("cookie file not found")

// httpClientOptions http client options
type httpClientOptions struct {
	dialTimeout           time.Duration
	dialKeepAlive         time.Duration
	fallbackDelay         time.Duration
	maxIdleConns          int
	maxIdleConnsPerHost   int
	maxConnsPerHost       int
	idleConnTimeout       time.Duration
	tlsConfig             *tls.Config
	tlsHandshakeTimeout   time.Duration
	expectContinueTimeout time.Duration
}

// HTTPClientOption configures how we set up the http client
type HTTPClientOption interface {
	apply(options *httpClientOptions)
}

// funcHTTPClientOption implements http client option
type funcHTTPClientOption struct {
	f func(options *httpClientOptions)
}

func (fo *funcHTTPClientOption) apply(o *httpClientOptions) {
	fo.f(o)
}

func newFuncHTTPOption(f func(options *httpClientOptions)) *funcHTTPClientOption {
	return &funcHTTPClientOption{f: f}
}

// WithHTTPDialTimeout specifies the `DialTimeout` to net.Dialer.
func WithHTTPDialTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.dialTimeout = d
	})
}

// WithHTTPDialKeepAlive specifies the `KeepAlive` to net.Dialer.
func WithHTTPDialKeepAlive(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.dialKeepAlive = d
	})
}

// WithHTTPDialFallbackDelay specifies the `FallbackDelay` to net.Dialer.
func WithHTTPDialFallbackDelay(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.fallbackDelay = d
	})
}

// WithHTTPMaxIdleConns specifies the `MaxIdleConns` to http client.
func WithHTTPMaxIdleConns(n int) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.maxIdleConns = n
	})
}

// WithHTTPMaxIdleConnsPerHost specifies the `MaxIdleConnsPerHost` to http client.
func WithHTTPMaxIdleConnsPerHost(n int) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.maxIdleConnsPerHost = n
	})
}

// WithHTTPMaxConnsPerHost specifies the `MaxConnsPerHost` to http client.
func WithHTTPMaxConnsPerHost(n int) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.maxConnsPerHost = n
	})
}

// WithHTTPIdleConnTimeout specifies the `IdleConnTimeout` to http client.
func WithHTTPIdleConnTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.idleConnTimeout = d
	})
}

// WithHTTPTLSConfig specifies the `TLSClientConfig` to http client.
func WithHTTPTLSConfig(c *tls.Config) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.tlsConfig = c
	})
}

// WithHTTPTLSHandshakeTimeout specifies the `TLSHandshakeTimeout` to http client.
func WithHTTPTLSHandshakeTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.tlsHandshakeTimeout = d
	})
}

// WithHTTPExpectContinueTimeout specifies the `ExpectContinueTimeout` to http client.
func WithHTTPExpectContinueTimeout(d time.Duration) HTTPClientOption {
	return newFuncHTTPOption(func(o *httpClientOptions) {
		o.expectContinueTimeout = d
	})
}

// httpRequestOptions http request options
type httpRequestOptions struct {
	headers          map[string]string
	cookieFile       string
	withCookies      bool
	cookieSave       bool
	cookieReplace    bool
	disableKeepAlive bool
	timeout          time.Duration
}

// HTTPRequestOption configures how we set up the http request
type HTTPRequestOption interface {
	apply(*httpRequestOptions) error
}

// funcHTTPRequestOption implements request option
type funcHTTPRequestOption struct {
	f func(*httpRequestOptions) error
}

func (fo *funcHTTPRequestOption) apply(r *httpRequestOptions) error {
	return fo.f(r)
}

func newFuncHTTPRequestOption(f func(*httpRequestOptions) error) *funcHTTPRequestOption {
	return &funcHTTPRequestOption{f: f}
}

// WithRequestHeader specifies the headers to http request.
func WithRequestHeader(key, value string) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.headers[key] = value

		return nil
	})
}

// WithRequestCookieFile specifies the file which to save http response cookies.
func WithRequestCookieFile(file string) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		path, err := filepath.Abs(file)

		if err != nil {
			return err
		}

		o.cookieFile = path

		return mkCookieFile(path)
	})
}

// WithRequestCookies specifies http requested with cookies.
func WithRequestCookies(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.withCookies = b

		return nil
	})
}

// WithRequestCookieSave specifies save the http response cookies.
func WithRequestCookieSave(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.cookieSave = b

		return nil
	})
}

// WithRequestCookieReplace specifies replace the old http response cookies.
func WithRequestCookieReplace(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.cookieReplace = b

		return nil
	})
}

// WithRequestDisableKeepAlive specifies close the connection after
// replying to this request (for servers) or after sending this
// request and reading its response (for clients).
func WithRequestDisableKeepAlive(b bool) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.disableKeepAlive = b

		return nil
	})
}

// WithRequestTimeout specifies the timeout to http request.
func WithRequestTimeout(d time.Duration) HTTPRequestOption {
	return newFuncHTTPRequestOption(func(o *httpRequestOptions) error {
		o.timeout = d

		return nil
	})
}

// HTTPClient http client
type HTTPClient struct {
	client *http.Client
}

// Get http get request
func (h *HTTPClient) Get(url string, options ...HTTPRequestOption) ([]byte, error) {
	req, err := http.NewRequest("GET", url, nil)

	if err != nil {
		return nil, err
	}

	o := &httpRequestOptions{
		headers: make(map[string]string),
		timeout: defaultHTTPTimeout,
	}

	if len(options) > 0 {
		for _, option := range options {
			if err := option.apply(o); err != nil {
				return nil, err
			}
		}
	}

	if len(o.headers) > 0 {
		for k, v := range o.headers {
			req.Header.Set(k, v)
		}
	}

	if o.withCookies {
		cookies, err := getCookies(o.cookieFile)

		if err != nil {
			return nil, err
		}

		for _, c := range cookies {
			req.AddCookie(c)
		}
	}

	if o.disableKeepAlive {
		req.Close = true
	}

	ctx, cancel := context.WithTimeout(context.TODO(), o.timeout)

	defer cancel()

	resp, err := h.client.Do(req.WithContext(ctx))

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if o.cookieSave {
		if err := saveCookie(resp.Cookies(), o.cookieFile, o.cookieReplace); err != nil {
			return nil, err
		}
	}

	if resp.StatusCode != http.StatusOK {
		io.Copy(ioutil.Discard, resp.Body)

		return nil, fmt.Errorf("error http code: %d", resp.StatusCode)
	}

	b, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return nil, err
	}

	return b, nil
}

// Post http post request
func (h *HTTPClient) Post(url string, body []byte, options ...HTTPRequestOption) ([]byte, error) {
	req, err := http.NewRequest("POST", url, bytes.NewReader(body))

	if err != nil {
		return nil, err
	}

	o := &httpRequestOptions{
		headers: make(map[string]string),
		timeout: defaultHTTPTimeout,
	}

	if len(options) > 0 {
		for _, option := range options {
			if err := option.apply(o); err != nil {
				return nil, err
			}
		}
	}

	if len(o.headers) > 0 {
		for k, v := range o.headers {
			req.Header.Set(k, v)
		}
	}

	if o.withCookies {
		cookies, err := getCookies(o.cookieFile)

		if err != nil {
			return nil, err
		}

		for _, c := range cookies {
			req.AddCookie(c)
		}
	}

	if o.disableKeepAlive {
		req.Close = true
	}

	ctx, cancel := context.WithTimeout(context.TODO(), o.timeout)

	defer cancel()

	resp, err := h.client.Do(req.WithContext(ctx))

	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	if o.cookieSave {
		if err := saveCookie(resp.Cookies(), o.cookieFile, o.cookieReplace); err != nil {
			return nil, err
		}
	}

	if resp.StatusCode != http.StatusOK {
		io.Copy(ioutil.Discard, resp.Body)

		return nil, fmt.Errorf("error http code: %d", resp.StatusCode)
	}

	b, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return nil, err
	}

	return b, nil
}

// defaultHTTPClient default http client
var defaultHTTPClient = &HTTPClient{
	client: &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyFromEnvironment,
			DialContext: (&net.Dialer{
				Timeout:   30 * time.Second,
				KeepAlive: 60 * time.Second,
			}).DialContext,
			MaxIdleConns:          0,
			MaxIdleConnsPerHost:   1000,
			MaxConnsPerHost:       1000,
			IdleConnTimeout:       60 * time.Second,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: 1 * time.Second,
		},
	},
}

// NewHTTPClient returns a new http client
func NewHTTPClient(options ...HTTPClientOption) *HTTPClient {
	o := &httpClientOptions{
		dialTimeout:           30 * time.Second,
		dialKeepAlive:         60 * time.Second,
		maxIdleConns:          0,
		maxIdleConnsPerHost:   1000,
		maxConnsPerHost:       1000,
		idleConnTimeout:       60 * time.Second,
		tlsHandshakeTimeout:   10 * time.Second,
		expectContinueTimeout: 1 * time.Second,
	}

	if len(options) > 0 {
		for _, option := range options {
			option.apply(o)
		}
	}

	t := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   o.dialTimeout,
			KeepAlive: o.dialKeepAlive,
		}).DialContext,
		MaxConnsPerHost:       o.maxConnsPerHost,
		MaxIdleConnsPerHost:   o.maxIdleConnsPerHost,
		MaxIdleConns:          o.maxIdleConns,
		IdleConnTimeout:       o.idleConnTimeout,
		TLSClientConfig:       o.tlsConfig,
		TLSHandshakeTimeout:   o.tlsHandshakeTimeout,
		ExpectContinueTimeout: o.expectContinueTimeout,
	}

	c := &HTTPClient{
		client: &http.Client{
			Transport: t,
		},
	}

	return c
}

// HTTPGet http get request
func HTTPGet(url string, options ...HTTPRequestOption) ([]byte, error) {
	return defaultHTTPClient.Get(url, options...)
}

// HTTPPost http post request
func HTTPPost(url string, body []byte, options ...HTTPRequestOption) ([]byte, error) {
	return defaultHTTPClient.Post(url, body, options...)
}

// mkCookieFile create cookie file
func mkCookieFile(path string) error {
	dir := filepath.Dir(path)

	// make dir if not exsit
	if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
		if err := os.MkdirAll(dir, os.ModePerm); err != nil {
			return err
		}
	}

	// create file if not exsit
	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
		if _, err := os.Create(path); err != nil {
			return err
		}
	}

	return nil
}

// getCookie get http saved cookies
func getCookies(cookieFile string) ([]*http.Cookie, error) {
	if cookieFile == "" {
		return nil, errCookieFileNotFound
	}

	cookieM := make(map[string]*http.Cookie)
	content, err := ioutil.ReadFile(cookieFile)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(content, &cookieM); err != nil {
		return nil, err
	}

	cookies := make([]*http.Cookie, 0, len(cookieM))

	for _, v := range cookieM {
		cookies = append(cookies, v)
	}

	return cookies, nil
}

// saveCookie save http cookies
func saveCookie(cookies []*http.Cookie, cookieFile string, replace bool) error {
	if len(cookies) == 0 {
		return nil
	}

	if cookieFile == "" {
		return errCookieFileNotFound
	}

	cookieM := make(map[string]*http.Cookie)

	if !replace {
		content, err := ioutil.ReadFile(cookieFile)

		if err != nil {
			return err
		}

		if len(content) > 0 {
			if err := json.Unmarshal(content, &cookieM); err != nil {
				return err
			}
		}
	}

	for _, c := range cookies {
		cookieM[c.Name] = c
	}

	b, err := json.Marshal(cookieM)

	if err != nil {
		return err
	}

	return ioutil.WriteFile(cookieFile, b, os.ModePerm)
}
<|endoftext|>


<commit_before>package main

import (
	"strings"

	"github.com/motemen/gore/gocode"
)

func (s *Session) completeWord(line string, pos int) (string, []string, string) {
	if strings.HasPrefix(line, ":") {
		// complete commands
		if !strings.Contains(line[0:pos], " ") {
			pre, post := line[0:pos], line[pos:]

			result := []string{}
			for _, command := range commands {
				name := ":" + command.name
				if strings.HasPrefix(name, pre) {
					// having complete means that this command takes an argument (for now)
					if !strings.HasPrefix(post, " ") && command.arg != "" {
						name = name + " "
					}
					result = append(result, name)
				}
			}
			return "", result, post
		}

		// complete command arguments
		for _, command := range commands {
			if command.complete == nil {
				continue
			}

			cmdPrefix := ":" + command.name + " "
			if strings.HasPrefix(line, cmdPrefix) && pos >= len(cmdPrefix) {
				return cmdPrefix, command.complete(s, line[len(cmdPrefix):pos]), ""
			}
		}

		return "", nil, ""
	}

	if gocode.Available() == false {
		return "", nil, ""
	}

	// code completion
	pos, cands, err := s.completeCode(line, pos, true)
	if err != nil {
		errorf("completeCode: %s", err)
		return "", nil, ""
	}

	return line[0:pos], cands, ""
}

// completeCode does code completion within the session using gocode (https://github.com/nsf/gocode).
// in and pos specifies the current input and the cursor position (0 <= pos <= len(in)) respectively.
// If exprMode is set to true, the completion is done as an expression (e.g. appends "(" to functions).
// Return value keep specifies how many characters of in should be kept and candidates are what follow in[0:keep].
func (s *Session) completeCode(in string, pos int, exprMode bool) (keep int, candidates []string, err error) {
	s.clearQuickFix()

	source, err := s.source(false)
	if err != nil {
		return
	}

	// Kind of dirty hack :/
	p := strings.LastIndex(source, "}")
	editingSource := source[0:p] + in + source[p:]
	cursor := len(source[0:p]) + pos

	result, err := gocode.Query([]byte(editingSource), cursor)
	if err != nil {
		return
	}

	keep = pos - result.Cursor
	candidates = make([]string, 0, len(result.Candidates))
	for _, e := range result.Candidates {
		cand := e.Name
		if cand == printerName && e.Class == "func" {
			continue
		}
		if exprMode && e.Class == "func" {
			cand = cand + "("
		}
		candidates = append(candidates, cand)
	}

	return
}
<commit_msg>change tab key at the head of line to insert an indentation<commit_after>package main

import (
	"strings"

	"github.com/motemen/gore/gocode"
)

func (s *Session) completeWord(line string, pos int) (string, []string, string) {
	if strings.HasPrefix(line, ":") {
		// complete commands
		if !strings.Contains(line[0:pos], " ") {
			pre, post := line[0:pos], line[pos:]

			result := []string{}
			for _, command := range commands {
				name := ":" + command.name
				if strings.HasPrefix(name, pre) {
					// having complete means that this command takes an argument (for now)
					if !strings.HasPrefix(post, " ") && command.arg != "" {
						name = name + " "
					}
					result = append(result, name)
				}
			}
			return "", result, post
		}

		// complete command arguments
		for _, command := range commands {
			if command.complete == nil {
				continue
			}

			cmdPrefix := ":" + command.name + " "
			if strings.HasPrefix(line, cmdPrefix) && pos >= len(cmdPrefix) {
				return cmdPrefix, command.complete(s, line[len(cmdPrefix):pos]), ""
			}
		}

		return "", nil, ""
	}

	if gocode.Available() == false {
		return "", nil, ""
	}

	if strings.TrimSpace(line[:pos]) == "" {
		return "", []string{line[:pos] + indent}, line[pos:]
	}

	// code completion
	pos, cands, err := s.completeCode(line, pos, true)
	if err != nil {
		errorf("completeCode: %s", err)
		return "", nil, ""
	}

	return line[0:pos], cands, ""
}

// completeCode does code completion within the session using gocode (https://github.com/nsf/gocode).
// in and pos specifies the current input and the cursor position (0 <= pos <= len(in)) respectively.
// If exprMode is set to true, the completion is done as an expression (e.g. appends "(" to functions).
// Return value keep specifies how many characters of in should be kept and candidates are what follow in[0:keep].
func (s *Session) completeCode(in string, pos int, exprMode bool) (keep int, candidates []string, err error) {
	s.clearQuickFix()

	source, err := s.source(false)
	if err != nil {
		return
	}

	// Kind of dirty hack :/
	p := strings.LastIndex(source, "}")
	editingSource := source[0:p] + in + source[p:]
	cursor := len(source[0:p]) + pos

	result, err := gocode.Query([]byte(editingSource), cursor)
	if err != nil {
		return
	}

	keep = pos - result.Cursor
	candidates = make([]string, 0, len(result.Candidates))
	for _, e := range result.Candidates {
		cand := e.Name
		if cand == printerName && e.Class == "func" {
			continue
		}
		if exprMode && e.Class == "func" {
			cand = cand + "("
		}
		candidates = append(candidates, cand)
	}

	return
}
<|endoftext|>


<commit_before>// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package orm

import (
	"bytes"
	"database/sql"
	"errors"
	"fmt"
	"reflect"

	"github.com/issue9/orm/fetch"
)

// DB与Tx的共有接口，方便以下方法调用。
type engine interface {
	Dialect() Dialect
	Query(replace bool, query string, args ...interface{}) (*sql.Rows, error)
	Exec(replace bool, query string, args ...interface{}) (sql.Result, error)
	Prepare(replace bool, query string) (*sql.Stmt, error)
	Prefix() string
}

// 检测rval中与cols对应的字段都是有效的，且为非零值。
// 若cols的长度为0，返回false。
func checkCols(cols []*Column, rval reflect.Value) bool {
	if len(cols) == 0 {
		return false
	}

	for _, col := range cols {
		field := rval.FieldByName(col.GoName)
		if !field.IsValid() {
			return false
		}

		if reflect.Zero(col.GoType).Interface() == field.Interface() {
			return false
		}
	}
	return true
}

// 根据model中的主键或是唯一索引为sql产生where语句，
// 若两者都不存在，则返回错误信息。rval为struct的reflect.Value
func where(e engine, sql *bytes.Buffer, m *Model, rval reflect.Value) ([]interface{}, error) {
	ret := []interface{}{}

	if checkCols(m.PK, rval) {
		sql.WriteString(" WHERE ")
		for _, col := range m.PK {
			e.Dialect().Quote(sql, col.Name)
			sql.WriteString("=?")
			ret = append(ret, rval.FieldByName(col.GoName).Interface())
		}
		return ret, nil
	}

	// 若不存在pk，也不存在唯一约束
	for _, cols := range m.UniqueIndexes {
		if !checkCols(cols, rval) {
			continue
		}

		sql.WriteString(" WHERE ")
		for _, col := range cols {
			e.Dialect().Quote(sql, col.Name)
			sql.WriteString("=?")
			ret = append(ret, rval.FieldByName(col.GoName).Interface())
			sql.WriteString(" AND ")
		}
		sql.Truncate(sql.Len() - 5) // 去掉最后的" AND "五个字符
		return ret, nil
	} // end range m.UniqueIndexes

	return nil, errors.New("where:无法产生where部分语句")
}

// 创建一个数据表。v为一个结构体或是结构体指针。
func createOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("createOne:无效的v.Kind()")
	}

	d := e.Dialect()
	sql := bytes.NewBufferString("CREATE TABLE IF NOT EXISTS ")
	d.Quote(sql, e.Prefix()+m.Name)
	sql.WriteByte('(')
	d.AIColSQL(sql, m)
	d.NoAIColSQL(sql, m)
	d.ConstraintsSQL(sql, m)
	sql.Truncate(sql.Len() - 1)
	sql.WriteByte(')')

	_, err = e.Exec(false, sql.String())
	return err
}

// 根据v的pk或中唯一索引列查找一行数据，并赋值给v
func findOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("findOne:无效的v.Kind()")
	}

	sql := bytes.NewBufferString("SELECT * FROM ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)

	vals, err := where(e, sql, m, rval)
	if err != nil {
		return err
	}

	rows, err := e.Query(false, sql.String(), vals...)
	if err != nil {
		return err
	}
	defer rows.Close()

	return fetch.Obj(v, rows)
}

// 插入一个对象到数据库
// 以v中的主键或是唯一索引作为where条件语句。
// 自增字段，即使指定了值，也不会被添加
func insertOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("insertOne:无效的v.Kind()")
	}

	keys := make([]string, 0, len(m.Cols))
	vals := make([]interface{}, 0, len(m.Cols))
	for name, col := range m.Cols {
		field := rval.FieldByName(col.GoName)
		if !field.IsValid() {
			return fmt.Errorf("insertOne:未找到该名称[%v]的值", col.GoName)
		}

		// 在为零值的情况下，若该列是AI或是有默认值，则过滤掉。无论该零值是否为手动设置的。
		if reflect.Zero(col.GoType).Interface() == field.Interface() &&
			(col.IsAI() || col.HasDefault) {
			continue
		}

		keys = append(keys, name)
		vals = append(vals, field.Interface())
	}

	if len(keys) == 0 {
		return errors.New("insertOne:未指定任何插入的列数据")
	}

	sql := bytes.NewBufferString("INSERT INTO ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)

	sql.WriteByte('(')
	for _, col := range keys {
		e.Dialect().Quote(sql, col)
		sql.WriteByte(',')
	}
	sql.Truncate(sql.Len() - 1)
	sql.WriteString(")VALUES(")
	for range vals {
		sql.WriteString("?,")
	}
	sql.Truncate(sql.Len() - 1)
	sql.WriteByte(')')

	_, err = e.Exec(false, sql.String(), vals...)
	return err
}

// 更新一个对象
// 以v中的主键或是唯一索引作为where条件语句，其它值为更新值
func updateOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("updateOne:无效的v.Kind()")
	}

	sql := bytes.NewBufferString("UPDATE ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)
	sql.WriteString(" SET ")
	vals := make([]interface{}, 0, len(m.Cols))

	for name, col := range m.Cols {
		field := rval.FieldByName(col.GoName)
		if !field.IsValid() {
			return fmt.Errorf("updateOne:未找到该名称[%v]的值", col.GoName)
		}

		// 忽略零值，TODO:还需要对比默认值
		if reflect.Zero(col.GoType).Interface() == field.Interface() {
			continue
		}

		e.Dialect().Quote(sql, name)
		sql.WriteString("=?,")
		vals = append(vals, field.Interface())
	}
	sql.Truncate(sql.Len() - 1)

	whereVals, err := where(e, sql, m, rval)
	if err != nil {
		return err
	}
	vals = append(vals, whereVals...)

	_, err = e.Exec(false, sql.String(), vals...)
	return err
}

// 删除v表示的单个对象的内容
// 以v中的主键或是唯一索引作为where条件语句
func deleteOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("deleteOne:无效的v.Kind()")
	}

	sql := bytes.NewBufferString("DELETE FROM ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)

	vals, err := where(e, sql, m, rval)
	if err != nil {
		return err
	}

	_, err = e.Exec(false, sql.String(), vals...)
	return err
}

// 获取v对象的表名，v可以是一个结构体，也可以是一个字符串。
func getTableName(e engine, v interface{}) (string, error) {
	switch tbl := v.(type) {
	case string:
		return tbl, nil
	case []rune:
		return string(tbl), nil
	case []byte:
		return string(tbl), nil
	}

	m, err := newModel(v)
	if err != nil {
		return "", err
	}
	return m.Name, nil
}

func dropOne(e engine, v interface{}) error {
	tbl, err := getTableName(e, v)
	if err != nil {
		return err
	}

	sql := bytes.NewBufferString("DROP TABLE IF EXISTS ")
	e.Dialect().Quote(sql, e.Prefix()+tbl)
	_, err = e.Exec(false, sql.String())
	return err
}

func truncateOne(e engine, v interface{}) error {
	tbl, err := getTableName(e, v)
	if err != nil {
		return err
	}

	sql := e.Dialect().TruncateTableSQL(e.Prefix() + tbl)
	_, err = e.Exec(false, sql)
	return err
}

// 创建一个或多个数据表
func createMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := createOne(e, obj); err != nil {
			return err
		}
	}

	return nil
}

// 插入一个或多个数据
// v可以是对象或是对象数组
func insertMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := insertOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

// 查找多个数据
func findMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := findOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

// 更新一个或多个类型。
// 更新依据为每个对象的主键或是唯一索引列。
// 若不存在此两个类型的字段，则返回错误信息。
func updateMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := updateOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

// 删除指定的数据对象。
func deleteMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := deleteOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

func dropMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := dropOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

func truncateMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := truncateOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>将createOne与createMult合并<commit_after>// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package orm

import (
	"bytes"
	"database/sql"
	"errors"
	"fmt"
	"reflect"

	"github.com/issue9/orm/fetch"
)

// DB与Tx的共有接口，方便以下方法调用。
type engine interface {
	Dialect() Dialect
	Query(replace bool, query string, args ...interface{}) (*sql.Rows, error)
	Exec(replace bool, query string, args ...interface{}) (sql.Result, error)
	Prepare(replace bool, query string) (*sql.Stmt, error)
	Prefix() string
}

// 检测rval中与cols对应的字段都是有效的，且为非零值。
// 若cols的长度为0，返回false。
func checkCols(cols []*Column, rval reflect.Value) bool {
	if len(cols) == 0 {
		return false
	}

	for _, col := range cols {
		field := rval.FieldByName(col.GoName)
		if !field.IsValid() {
			return false
		}

		if reflect.Zero(col.GoType).Interface() == field.Interface() {
			return false
		}
	}
	return true
}

// 根据model中的主键或是唯一索引为sql产生where语句，
// 若两者都不存在，则返回错误信息。rval为struct的reflect.Value
func where(e engine, sql *bytes.Buffer, m *Model, rval reflect.Value) ([]interface{}, error) {
	ret := []interface{}{}

	if checkCols(m.PK, rval) {
		sql.WriteString(" WHERE ")
		for _, col := range m.PK {
			e.Dialect().Quote(sql, col.Name)
			sql.WriteString("=?")
			ret = append(ret, rval.FieldByName(col.GoName).Interface())
		}
		return ret, nil
	}

	// 若不存在pk，也不存在唯一约束
	for _, cols := range m.UniqueIndexes {
		if !checkCols(cols, rval) {
			continue
		}

		sql.WriteString(" WHERE ")
		for _, col := range cols {
			e.Dialect().Quote(sql, col.Name)
			sql.WriteString("=?")
			ret = append(ret, rval.FieldByName(col.GoName).Interface())
			sql.WriteString(" AND ")
		}
		sql.Truncate(sql.Len() - 5) // 去掉最后的" AND "五个字符
		return ret, nil
	} // end range m.UniqueIndexes

	return nil, errors.New("where:无法产生where部分语句")
}

// 根据v的pk或中唯一索引列查找一行数据，并赋值给v
func findOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("findOne:无效的v.Kind()")
	}

	sql := bytes.NewBufferString("SELECT * FROM ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)

	vals, err := where(e, sql, m, rval)
	if err != nil {
		return err
	}

	rows, err := e.Query(false, sql.String(), vals...)
	if err != nil {
		return err
	}
	defer rows.Close()

	return fetch.Obj(v, rows)
}

// 插入一个对象到数据库
// 以v中的主键或是唯一索引作为where条件语句。
// 自增字段，即使指定了值，也不会被添加
func insertOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("insertOne:无效的v.Kind()")
	}

	keys := make([]string, 0, len(m.Cols))
	vals := make([]interface{}, 0, len(m.Cols))
	for name, col := range m.Cols {
		field := rval.FieldByName(col.GoName)
		if !field.IsValid() {
			return fmt.Errorf("insertOne:未找到该名称[%v]的值", col.GoName)
		}

		// 在为零值的情况下，若该列是AI或是有默认值，则过滤掉。无论该零值是否为手动设置的。
		if reflect.Zero(col.GoType).Interface() == field.Interface() &&
			(col.IsAI() || col.HasDefault) {
			continue
		}

		keys = append(keys, name)
		vals = append(vals, field.Interface())
	}

	if len(keys) == 0 {
		return errors.New("insertOne:未指定任何插入的列数据")
	}

	sql := bytes.NewBufferString("INSERT INTO ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)

	sql.WriteByte('(')
	for _, col := range keys {
		e.Dialect().Quote(sql, col)
		sql.WriteByte(',')
	}
	sql.Truncate(sql.Len() - 1)
	sql.WriteString(")VALUES(")
	for range vals {
		sql.WriteString("?,")
	}
	sql.Truncate(sql.Len() - 1)
	sql.WriteByte(')')

	_, err = e.Exec(false, sql.String(), vals...)
	return err
}

// 更新一个对象
// 以v中的主键或是唯一索引作为where条件语句，其它值为更新值
func updateOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("updateOne:无效的v.Kind()")
	}

	sql := bytes.NewBufferString("UPDATE ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)
	sql.WriteString(" SET ")
	vals := make([]interface{}, 0, len(m.Cols))

	for name, col := range m.Cols {
		field := rval.FieldByName(col.GoName)
		if !field.IsValid() {
			return fmt.Errorf("updateOne:未找到该名称[%v]的值", col.GoName)
		}

		// 忽略零值，TODO:还需要对比默认值
		if reflect.Zero(col.GoType).Interface() == field.Interface() {
			continue
		}

		e.Dialect().Quote(sql, name)
		sql.WriteString("=?,")
		vals = append(vals, field.Interface())
	}
	sql.Truncate(sql.Len() - 1)

	whereVals, err := where(e, sql, m, rval)
	if err != nil {
		return err
	}
	vals = append(vals, whereVals...)

	_, err = e.Exec(false, sql.String(), vals...)
	return err
}

// 删除v表示的单个对象的内容
// 以v中的主键或是唯一索引作为where条件语句
func deleteOne(e engine, v interface{}) error {
	m, err := newModel(v)
	if err != nil {
		return err
	}

	rval := reflect.ValueOf(v)
	for rval.Kind() == reflect.Ptr {
		rval = rval.Elem()
	}

	if rval.Kind() != reflect.Struct {
		return errors.New("deleteOne:无效的v.Kind()")
	}

	sql := bytes.NewBufferString("DELETE FROM ")
	e.Dialect().Quote(sql, e.Prefix()+m.Name)

	vals, err := where(e, sql, m, rval)
	if err != nil {
		return err
	}

	_, err = e.Exec(false, sql.String(), vals...)
	return err
}

// 获取v对象的表名，v可以是一个结构体，也可以是一个字符串。
func getTableName(e engine, v interface{}) (string, error) {
	switch tbl := v.(type) {
	case string:
		return tbl, nil
	case []rune:
		return string(tbl), nil
	case []byte:
		return string(tbl), nil
	}

	m, err := newModel(v)
	if err != nil {
		return "", err
	}
	return m.Name, nil
}

func dropOne(e engine, v interface{}) error {
	tbl, err := getTableName(e, v)
	if err != nil {
		return err
	}

	sql := bytes.NewBufferString("DROP TABLE IF EXISTS ")
	e.Dialect().Quote(sql, e.Prefix()+tbl)
	_, err = e.Exec(false, sql.String())
	return err
}

func truncateOne(e engine, v interface{}) error {
	tbl, err := getTableName(e, v)
	if err != nil {
		return err
	}

	sql := e.Dialect().TruncateTableSQL(e.Prefix() + tbl)
	_, err = e.Exec(false, sql)
	return err
}

// 创建一个或多个数据表
func createMult(e engine, objs ...interface{}) error {
	sql := new(bytes.Buffer)
	d := e.Dialect()

	for i, v := range objs {
		m, err := newModel(v)
		if err != nil {
			return err
		}

		rval := reflect.ValueOf(v)
		for rval.Kind() == reflect.Ptr {
			rval = rval.Elem()
		}

		if rval.Kind() != reflect.Struct {
			return fmt.Errorf("createMult:objs[%v]类型必须为结构体或是结构体指针", i)
		}

		sql.Reset()
		sql.WriteString("CREATE TABLE IF NOT EXISTS ")
		d.Quote(sql, e.Prefix()+m.Name)
		sql.WriteByte('(')
		d.AIColSQL(sql, m)
		d.NoAIColSQL(sql, m)
		d.ConstraintsSQL(sql, m)
		sql.Truncate(sql.Len() - 1)
		sql.WriteByte(')')

		if _, err = e.Exec(false, sql.String()); err != nil {
			return err
		}
	}

	return nil
}

// 插入一个或多个数据
// v可以是对象或是对象数组
func insertMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := insertOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

// 查找多个数据
func findMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := findOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

// 更新一个或多个类型。
// 更新依据为每个对象的主键或是唯一索引列。
// 若不存在此两个类型的字段，则返回错误信息。
func updateMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := updateOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

// 删除指定的数据对象。
func deleteMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := deleteOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

func dropMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := dropOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}

func truncateMult(e engine, objs ...interface{}) error {
	for _, obj := range objs {
		if err := truncateOne(e, obj); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package vcfgo

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
)

type InfoByte struct {
	info   []byte
	header *Header
}

func NewInfoByte(info string, h *Header) *InfoByte {
	return &InfoByte{info: []byte(info), header: h}
}

// return the start and end positions of the value.
// for flag the value is the flag.
func getpositions(info []byte, key string) (start, end int) {
	bkey := []byte(key)
	pos := 0
	for {
		if pos >= len(info) {
			return -1, -1
		}
		ipos := bytes.Index(info[pos:], bkey)
		if ipos == -1 {
			return -1, -1
		}
		pos += ipos
		if pos != 0 && info[pos-1] != ';' {
			pos += 1
			continue
		}
		eq := pos + bytes.IndexByte(info[pos:], byte('='))
		// at end of field and we found an Flag
		var semi int
		if eq == -1 {
			return pos, len(info)
		} else if eq-pos != len(bkey) {
			// found a longer key with same prefix.
			semi = bytes.IndexByte(info[pos:], byte(';'))
			// flag field
			if semi == -1 {
				semi = len(info)
			} else {
				semi += pos
			}
			if semi-pos == len(bkey) {
				return pos, semi - 1
			}
			pos = semi + 1
			continue
		} else {
			semi = bytes.IndexByte(info[pos+1:], byte(';'))
		}
		if semi > -1 && eq > pos+semi {
			// should be a flag.
			return pos, pos + semi
		}

		// not at end of info field
		if semi != -1 {
			semi += pos
		}
		return eq + 1, semi
	}
}

func (i InfoByte) Contains(key string) bool {
	// short-circuit common case.
	if !bytes.Contains(i.info, []byte(key+"=")) {
		return false
	}
	s, _ := getpositions(i.info, key)
	return s != -1
}

func (i InfoByte) Keys() []string {
	sp := bytes.Split(i.info, []byte{';'})
	keys := make([]string, 0, len(sp))
	for _, pair := range sp {
		key := bytes.SplitN(pair, []byte{'='}, 2)[0]
		keys = append(keys, string(key))
	}
	return keys

}

func (i *InfoByte) Delete(key string) {
	s, e := getpositions(i.info, key)
	if s == -1 {
		return
	}
	// check if it's a flag
	if s != 0 && i.info[s-1] != ';' {
		s -= (len(key) + 1)
	}
	if s < 0 {
		s = 0
	}
	if e == -1 {
		e = len(i.info)
	} else {
		e += 2
	}
	if s == 0 && e == len(i.info) {
		i.info = i.info[:0]
	} else if e < len(i.info) {
		i.info = append(i.info[:s], i.info[e:]...)
	} else {
		i.info = i.info[:s-1]
	}
}

func ItoS(k string, v interface{}) string {
	if _, ok := v.(bool); ok {
		return k
	} else {
		switch v.(type) {
		case float32:
			return fmtFloat32(v.(float32))
		case float64:
			return fmtFloat64(v.(float64))
		case int:
			return fmt.Sprintf("%d", v.(int))
		case uint32:
			return fmt.Sprintf("%d", v.(uint32))
		case []interface{}:
			vals := v.([]interface{})
			svals := make([]string, len(vals))
			switch vals[0].(type) {
			case float64:
				for i, val := range vals {
					svals[i] = fmtFloat64(val.(float64))
				}
			case float32:
				for i, val := range vals {
					svals[i] = fmtFloat32(val.(float32))
				}
			case int:
				for i, val := range vals {
					svals[i] = strconv.Itoa(val.(int))
				}
			default:
				for i, val := range vals {
					svals[i] = fmt.Sprintf("%v", val)
				}
			}
			return strings.Join(svals, ",")

		default:
			return v.(string)
		}
	}
}

func (i InfoByte) SGet(key string) []byte {
	var sub []byte
	if key == "" || len(i.info) == 1 {
		return sub
	}
	start, end := getpositions(i.info, key)
	if start == -1 {
		return sub
	}
	if end == -1 {
		end = len(i.info) - 1
	}
	val := i.info[start : end+1]
	return val
}

// Get a value from the bytes typed according to the header.
func (i InfoByte) Get(key string) (interface{}, error) {
	v := string(i.SGet(key))
	skey := string(key)
	hi, ok := i.header.Infos[key]
	if !ok {
		err := fmt.Errorf("Info Error: %s not found in header", skey)
		// flag
		if skey == v {
			return true, err
		}
		return v, err
	}

	if len(v) == 0 {
		var err error
		var val interface{} = nil
		if hi.Type != "Flag" {
			err = fmt.Errorf("Info Error: %s not found in row", skey)
		} else {
			val = false
		}
		return val, err
	}
	if v == key {
		var err error
		if hi.Type != "Flag" {
			err = fmt.Errorf("Info Error: flag field (%s) should be specified as such in the header", skey)
		}
		return true, err
	}

	var err error
	var iv interface{}

	switch hi.Number {

	case "0":
		if hi.Type != "Flag" {
			err = fmt.Errorf("Info Error: flag field (%s) should have Number=0", skey)
		}
		return true, err

	case "1":
		return parseOne(skey, v, hi.Type)

	case "R", "A", "G", "2", "3", ".":
		vals := strings.Split(v, ",")
		var vi interface{} = make([]interface{}, len(vals))
		for j, val := range vals {
			iv, err = parseOne(skey, val, hi.Type)
			vi.([]interface{})[j] = iv
		}
		return vi, err

	default:
		vals := strings.Split(v, ",")
		var vi interface{} = make([]interface{}, len(vals))
		if _, err := strconv.Atoi(hi.Number); err == nil {
			for j, val := range vals {
				iv, err = parseOne(skey, val, hi.Type)
				vi.([]interface{})[j] = iv
			}
		} else {
			panic(fmt.Sprintf("found Number=%s", hi.Number))
		}
		return vi, err

	}
}

func (i InfoByte) String() string {
	return string(i.info)
}

func (i *InfoByte) UpdateHeader(key string, value interface{}) {
	if i.header != nil {
		switch value.(type) {
		case bool:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "0", Type: "Flag"}
		case string:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "1", Type: "Character"}
		case int, int32, int64, uint32, uint64:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "1", Type: "Integer"}
		case float32, float64:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "1", Type: "Float"}
		case []interface{}:
			v := value.([]interface{})[0]
			i.UpdateHeader(key, v)
		}
	}
}

func (i *InfoByte) Set(key string, value interface{}) {
	if len(i.info) == 0 {
		if v, ok := value.(bool); ok {
			if v {
				i.info = []byte(key)
			}
		} else {
			i.info = []byte(fmt.Sprintf("%s=%s", key, ItoS(key, value)))
		}
		return
	}
	s, e := getpositions(i.info, key)
	if s == -1 || s == len(i.info) {
		if b, ok := value.(bool); ok {
			if b {
				i.info = append(i.info, ';')
				i.info = append(i.info, key...)
			}
			return
		}
		slug := []byte(fmt.Sprintf(";%s=%s", key, ItoS(key, value)))
		i.info = append(i.info, slug...)
		i.UpdateHeader(key, value)
		return
	}
	if b, ok := value.(bool); ok {
		if !b {
			i.Delete(key)
		}
		return
	}
	slug := []byte(ItoS(key, value))
	if e == -1 {
		i.info = append(i.info[:s], slug...)
	} else {
		i.info = append(i.info[:s], append(slug, i.info[e+1:]...)...)
	}
}

func (i *InfoByte) Add(key string, value interface{}) {
	i.Set(key, value)
}
<commit_msg>panic removal<commit_after>package vcfgo

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
)

type InfoByte struct {
	info   []byte
	header *Header
}

func NewInfoByte(info string, h *Header) *InfoByte {
	return &InfoByte{info: []byte(info), header: h}
}

// return the start and end positions of the value.
// for flag the value is the flag.
func getpositions(info []byte, key string) (start, end int) {
	bkey := []byte(key)
	pos := 0
	for {
		if pos >= len(info) {
			return -1, -1
		}
		ipos := bytes.Index(info[pos:], bkey)
		if ipos == -1 {
			return -1, -1
		}
		pos += ipos
		if pos != 0 && info[pos-1] != ';' {
			pos += 1
			continue
		}
		eq := pos + bytes.IndexByte(info[pos:], byte('='))
		// at end of field and we found an Flag
		var semi int
		if eq == -1 {
			return pos, len(info)
		} else if eq-pos != len(bkey) {
			// found a longer key with same prefix.
			semi = bytes.IndexByte(info[pos:], byte(';'))
			// flag field
			if semi == -1 {
				semi = len(info)
			} else {
				semi += pos
			}
			if semi-pos == len(bkey) {
				return pos, semi - 1
			}
			pos = semi + 1
			continue
		} else {
			semi = bytes.IndexByte(info[pos+1:], byte(';'))
		}
		if semi > -1 && eq > pos+semi {
			// should be a flag.
			return pos, pos + semi
		}

		// not at end of info field
		if semi != -1 {
			semi += pos
		}
		return eq + 1, semi
	}
}

func (i InfoByte) Contains(key string) bool {
	// short-circuit common case.
	if !bytes.Contains(i.info, []byte(key+"=")) {
		return false
	}
	s, _ := getpositions(i.info, key)
	return s != -1
}

func (i InfoByte) Keys() []string {
	sp := bytes.Split(i.info, []byte{';'})
	keys := make([]string, 0, len(sp))
	for _, pair := range sp {
		key := bytes.SplitN(pair, []byte{'='}, 2)[0]
		keys = append(keys, string(key))
	}
	return keys

}

func (i *InfoByte) Delete(key string) {
	s, e := getpositions(i.info, key)
	if s == -1 {
		return
	}
	// check if it's a flag
	if s != 0 && i.info[s-1] != ';' {
		s -= (len(key) + 1)
	}
	if s < 0 {
		s = 0
	}
	if e == -1 {
		e = len(i.info)
	} else {
		e += 2
	}
	if s == 0 && e == len(i.info) {
		i.info = i.info[:0]
	} else if e < len(i.info) {
		i.info = append(i.info[:s], i.info[e:]...)
	} else {
		i.info = i.info[:s-1]
	}
}

func ItoS(k string, v interface{}) string {
	if _, ok := v.(bool); ok {
		return k
	} else {
		switch v.(type) {
		case float32:
			return fmtFloat32(v.(float32))
		case float64:
			return fmtFloat64(v.(float64))
		case int:
			return fmt.Sprintf("%d", v.(int))
		case uint32:
			return fmt.Sprintf("%d", v.(uint32))
		case []interface{}:
			vals := v.([]interface{})
			svals := make([]string, len(vals))
			switch vals[0].(type) {
			case float64:
				for i, val := range vals {
					svals[i] = fmtFloat64(val.(float64))
				}
			case float32:
				for i, val := range vals {
					svals[i] = fmtFloat32(val.(float32))
				}
			case int:
				for i, val := range vals {
					svals[i] = strconv.Itoa(val.(int))
				}
			default:
				for i, val := range vals {
					svals[i] = fmt.Sprintf("%v", val)
				}
			}
			return strings.Join(svals, ",")

		default:
			return v.(string)
		}
	}
}

func (i InfoByte) SGet(key string) []byte {
	var sub []byte
	if key == "" || len(i.info) == 1 {
		return sub
	}
	start, end := getpositions(i.info, key)
	if start == -1 {
		return sub
	}
	if end == -1 {
		end = len(i.info) - 1
	}
	val := i.info[start : end+1]
	return val
}

// Get a value from the bytes typed according to the header.
func (i InfoByte) Get(key string) (interface{}, error) {
	v := string(i.SGet(key))
	skey := string(key)
	hi, ok := i.header.Infos[key]
	if !ok {
		err := fmt.Errorf("Info Error: %s not found in header", skey)
		// flag
		if skey == v {
			return true, err
		}
		return v, err
	}

	if len(v) == 0 {
		var err error
		var val interface{} = nil
		if hi.Type != "Flag" {
			err = fmt.Errorf("Info Error: %s not found in row", skey)
		} else {
			val = false
		}
		return val, err
	}
	if v == key {
		var err error
		if hi.Type != "Flag" {
			err = fmt.Errorf("Info Error: flag field (%s) should be specified as such in the header", skey)
		}
		return true, err
	}

	var err error
	var iv interface{}

	switch hi.Number {

	case "0":
		if hi.Type != "Flag" {
			err = fmt.Errorf("Info Error: flag field (%s) should have Number=0", skey)
		}
		return true, err

	case "1":
		return parseOne(skey, v, hi.Type)

	case "R", "A", "G", "2", "3", ".":
		vals := strings.Split(v, ",")
		var vi interface{} = make([]interface{}, len(vals))
		for j, val := range vals {
			iv, err = parseOne(skey, val, hi.Type)
			vi.([]interface{})[j] = iv
		}
		return vi, err

	default:
		vals := strings.Split(v, ",")
		var vi interface{} = make([]interface{}, len(vals))
		if _, err := strconv.Atoi(hi.Number); err == nil {
			for j, val := range vals {
				iv, err = parseOne(skey, val, hi.Type)
				vi.([]interface{})[j] = iv
			}
		}
		return vi, err

	}
}

func (i InfoByte) String() string {
	return string(i.info)
}

func (i *InfoByte) UpdateHeader(key string, value interface{}) {
	if i.header != nil {
		switch value.(type) {
		case bool:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "0", Type: "Flag"}
		case string:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "1", Type: "Character"}
		case int, int32, int64, uint32, uint64:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "1", Type: "Integer"}
		case float32, float64:
			i.header.Infos[key] = &Info{Id: key, Description: key, Number: "1", Type: "Float"}
		case []interface{}:
			v := value.([]interface{})[0]
			i.UpdateHeader(key, v)
		}
	}
}

func (i *InfoByte) Set(key string, value interface{}) {
	if len(i.info) == 0 {
		if v, ok := value.(bool); ok {
			if v {
				i.info = []byte(key)
			}
		} else {
			i.info = []byte(fmt.Sprintf("%s=%s", key, ItoS(key, value)))
		}
		return
	}
	s, e := getpositions(i.info, key)
	if s == -1 || s == len(i.info) {
		if b, ok := value.(bool); ok {
			if b {
				i.info = append(i.info, ';')
				i.info = append(i.info, key...)
			}
			return
		}
		slug := []byte(fmt.Sprintf(";%s=%s", key, ItoS(key, value)))
		i.info = append(i.info, slug...)
		i.UpdateHeader(key, value)
		return
	}
	if b, ok := value.(bool); ok {
		if !b {
			i.Delete(key)
		}
		return
	}
	slug := []byte(ItoS(key, value))
	if e == -1 {
		i.info = append(i.info[:s], slug...)
	} else {
		i.info = append(i.info[:s], append(slug, i.info[e+1:]...)...)
	}
}

func (i *InfoByte) Add(key string, value interface{}) {
	i.Set(key, value)
}
<|endoftext|>