<commit_before>package web

import (
    "bytes"
    "container/vector"
    "crypto/hmac"
    "encoding/base64"
    "fmt"
    "http"
    "io/ioutil"
    "log"
    "os"
    "path"
    "reflect"
    "regexp"
    "runtime"
    "strconv"
    "strings"
    "time"
)

type conn interface {
    StartResponse(status int)
    SetHeader(hdr string, val string, unique bool)
    Write(data []byte) (n int, err os.Error)
    Close()
}

type Context struct {
    *Request
    *Server
    conn
    responseStarted bool
}

func (ctx *Context) StartResponse(status int) {
    ctx.conn.StartResponse(status)
    ctx.responseStarted = true
}

func (ctx *Context) Write(data []byte) (n int, err os.Error) {
    if !ctx.responseStarted {
        ctx.StartResponse(200)
    }

    //if it's a HEAD request, we just write blank data
    if ctx.Request.Method == "HEAD" {
        data = []byte{}
    }

    return ctx.conn.Write(data)
}
func (ctx *Context) WriteString(content string) {
    ctx.Write([]byte(content))
}

func (ctx *Context) Abort(status int, body string) {
    ctx.StartResponse(status)
    ctx.WriteString(body)
}

func (ctx *Context) Redirect(status int, url string) {
    ctx.SetHeader("Location", url, true)
    ctx.StartResponse(status)
    ctx.WriteString("Redirecting to: " + url)
}

func (ctx *Context) NotModified() {
    ctx.StartResponse(304)
}

func (ctx *Context) NotFound(message string) {
    ctx.StartResponse(404)
    ctx.WriteString(message)
}

//Sets a cookie -- duration is the amount of time in seconds. 0 = forever
func (ctx *Context) SetCookie(name string, value string, age int64) {
    if age == 0 {
        //do some really long time
    }

    utctime := time.UTC()
    utc1 := time.SecondsToUTC(utctime.Seconds() + 60*30)
    cookie := fmt.Sprintf("%s=%s; expires=%s", name, value, webTime(utc1))
    ctx.SetHeader("Set-Cookie", cookie, false)
}

func getCookieSig(key string, val []byte, timestamp string) string {
    hm := hmac.NewSHA1([]byte(key))

    hm.Write(val)
    hm.Write([]byte(timestamp))

    hex := fmt.Sprintf("%02x", hm.Sum())
    return hex
}

func (ctx *Context) SetSecureCookie(name string, val string, age int64) {
    //base64 encode the val
    if len(ctx.Server.Config.CookieSecret) == 0 {
        ctx.Logger.Println("Secret Key for secure cookies has not been set. Please call web.SetCookieSecret")
        return
    }
    var buf bytes.Buffer
    encoder := base64.NewEncoder(base64.StdEncoding, &buf)
    encoder.Write([]byte(val))
    encoder.Close()
    vs := buf.String()
    vb := buf.Bytes()
    timestamp := strconv.Itoa64(time.Seconds())
    sig := getCookieSig(ctx.Server.Config.CookieSecret, vb, timestamp)
    cookie := strings.Join([]string{vs, timestamp, sig}, "|")
    ctx.SetCookie(name, cookie, age)
}

func (ctx *Context) GetSecureCookie(name string) (string, bool) {
    cookie, ok := ctx.Request.Cookies[name]
    if !ok {
        return "", false
    }

    parts := strings.Split(cookie, "|", 3)

    val := parts[0]
    timestamp := parts[1]
    sig := parts[2]

    if getCookieSig(ctx.Server.Config.CookieSecret, []byte(val), timestamp) != sig {
        return "", false
    }

    ts, _ := strconv.Atoi64(timestamp)

    if time.Seconds()-31*86400 > ts {
        return "", false
    }

    buf := bytes.NewBufferString(val)
    encoder := base64.NewDecoder(base64.StdEncoding, buf)

    res, _ := ioutil.ReadAll(encoder)
    return string(res), true
}

// small optimization: cache the context type instead of repeteadly calling reflect.Typeof
var contextType reflect.Type

var exeFile string

// default
func defaultStaticDir() string {
    root, _ := path.Split(exeFile)
    return path.Join(root, "static")
}

func init() {
    contextType = reflect.Typeof(Context{})
    //find the location of the exe file
    arg0 := path.Clean(os.Args[0])
    wd, _ := os.Getwd()
    if strings.HasPrefix(arg0, "/") {
        exeFile = arg0
    } else {
        //TODO for robustness, search each directory in $PATH
        exeFile = path.Join(wd, arg0)
    }
}

type route struct {
    r       string
    cr      *regexp.Regexp
    method  string
    handler *reflect.FuncValue
}

func (s *Server) addRoute(r string, method string, handler interface{}) {
    cr, err := regexp.Compile(r)
    if err != nil {
        s.Logger.Printf("Error in route regex %q\n", r)
        return
    }
    fv := reflect.NewValue(handler).(*reflect.FuncValue)
    s.routes.Push(route{r, cr, method, fv})
}

type httpConn struct {
    conn http.ResponseWriter
}

func (c *httpConn) StartResponse(status int) { c.conn.WriteHeader(status) }

func (c *httpConn) SetHeader(hdr string, val string, unique bool) {
    //right now unique can't be implemented through the http package.
    //see issue 488
    c.conn.SetHeader(hdr, val)
}

func (c *httpConn) WriteString(content string) {
    buf := bytes.NewBufferString(content)
    c.conn.Write(buf.Bytes())
}

func (c *httpConn) Write(content []byte) (n int, err os.Error) {
    return c.conn.Write(content)
}

func (c *httpConn) Close() {
    rwc, buf, _ := c.conn.Hijack()
    if buf != nil {
        buf.Flush()
    }

    if rwc != nil {
        rwc.Close()
    }
}

func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
    conn := httpConn{c}
    wreq := newRequest(req, c)
    s.routeHandler(wreq, &conn)
}

func (s *Server) safelyCall(function *reflect.FuncValue, args []reflect.Value) (resp []reflect.Value, e interface{}) {
    defer func() {
        if err := recover(); err != nil {
            if !s.Config.RecoverPanic {
                // go back to panic
                panic(err)
            } else {
                e = err
                resp = nil
                s.Logger.Println("Handler crashed with error", err)
                for i := 1; ; i += 1 {
                    _, file, line, ok := runtime.Caller(i)
                    if !ok {
                        break
                    }
                    s.Logger.Println(file, line)
                }
            }
        }
    }()
    return function.Call(args), nil
}

func (s *Server) routeHandler(req *Request, c conn) {
    requestPath := req.URL.Path

    //log the request
    if len(req.URL.RawQuery) == 0 {
        s.Logger.Println(req.Method + " " + requestPath)
    } else {
        s.Logger.Println(req.Method + " " + requestPath + "?" + req.URL.RawQuery)
    }

    //parse the form data (if it exists)
    perr := req.parseParams()
    if perr != nil {
        s.Logger.Printf("Failed to parse form data %q\n", perr.String())
    }

    //parse the cookies
    perr = req.parseCookies()
    if perr != nil {
        s.Logger.Printf("Failed to parse cookies %q", perr.String())
    }

    ctx := Context{req, s, c, false}

    //set some default headers
    ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)
    ctx.SetHeader("Server", "web.go", true)

    tm := time.UTC()
    ctx.SetHeader("Date", webTime(tm), true)

    //try to serve a static file
    staticDir := s.Config.StaticDir
    if staticDir == "" {
        staticDir = defaultStaticDir()
    }
    staticFile := path.Join(staticDir, requestPath)
    if fileExists(staticFile) && (req.Method == "GET" || req.Method == "HEAD") {
        serveFile(&ctx, staticFile)
        return
    }

    for i := 0; i < s.routes.Len(); i++ {
        route := s.routes.At(i).(route)
        cr := route.cr
        //if the methods don't match, skip this handler (except HEAD can be used in place of GET)
        if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") {
            continue
        }

        if !cr.MatchString(requestPath) {
            continue
        }
        match := cr.FindStringSubmatch(requestPath)

        if len(match[0]) != len(requestPath) {
            continue
        }

        var args vector.Vector

        handlerType := route.handler.Type().(*reflect.FuncType)

        //check if the first arg in the handler is a context type
        if handlerType.NumIn() > 0 {
            if a0, ok := handlerType.In(0).(*reflect.PtrType); ok {
                typ := a0.Elem()
                if typ == contextType {
                    args.Push(reflect.NewValue(&ctx))
                }
            }
        }

        for _, arg := range match[1:] {
            args.Push(reflect.NewValue(arg))
        }

        if args.Len() != handlerType.NumIn() {
            s.Logger.Printf("Incorrect number of arguments for %s\n", requestPath)
            ctx.Abort(500, "Server Error")
            return
        }

        valArgs := make([]reflect.Value, args.Len())
        for i := 0; i < args.Len(); i++ {
            valArgs[i] = args.At(i).(reflect.Value)
        }

        ret, err := s.safelyCall(route.handler, valArgs)
        if err != nil {
            //there was a panic in the handler
            ctx.Abort(500, "Server Error")
        }

        if len(ret) == 0 {
            return
        }

        sval, ok := ret[0].(*reflect.StringValue)

        if ok && !ctx.responseStarted {
            content := []byte(sval.Get())
            ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
            ctx.StartResponse(200)
            ctx.Write(content)
        }

        return
    }

    //try to serve index.html || index.htm
    if indexPath := path.Join(path.Join(staticDir, requestPath), "index.html"); fileExists(indexPath) {
        serveFile(&ctx, indexPath)
        return
    }

    if indexPath := path.Join(path.Join(staticDir, requestPath), "index.htm"); fileExists(indexPath) {
        serveFile(&ctx, indexPath)
        return
    }

    ctx.Abort(404, "Page not found")
}

var Config = &ServerConfig{
    RecoverPanic: true,
}

var mainServer = Server{
    Config: Config,
    Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
}

type Server struct {
    Config *ServerConfig
    routes vector.Vector
    Logger *log.Logger
}

func (s *Server) initServer() {
    if s.Config == nil {
        s.Config = &ServerConfig{}
    }

    if s.Logger == nil {
        s.Logger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
    }
}

func (s *Server) Run(addr string) {
    s.initServer()

    mux := http.NewServeMux()
    mux.Handle("/", s)
    s.Logger.Printf("web.go serving %s\n", addr)
    err := http.ListenAndServe(addr, mux)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}

//runs the web application and serves http requests
func Run(addr string) {
    mainServer.Run(addr)
}

func (s *Server) RunScgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving scgi %s\n", addr)
    s.listenAndServeScgi(addr)
}

//runs the web application and serves scgi requests
func RunScgi(addr string) {
    mainServer.RunScgi(addr)
}

func (s *Server) RunFcgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving fcgi %s\n", addr)
    s.listenAndServeFcgi(addr)
}

//runs the web application by serving fastcgi requests
func RunFcgi(addr string) {
    mainServer.RunFcgi(addr)
}

//Adds a handler for the 'GET' http method.
func (s *Server) Get(route string, handler interface{}) {
    s.addRoute(route, "GET", handler)
}

//Adds a handler for the 'POST' http method.
func (s *Server) Post(route string, handler interface{}) {
    s.addRoute(route, "POST", handler)
}

//Adds a handler for the 'PUT' http method.
func (s *Server) Put(route string, handler interface{}) {
    s.addRoute(route, "PUT", handler)
}

//Adds a handler for the 'DELETE' http method.
func (s *Server) Delete(route string, handler interface{}) {
    s.addRoute(route, "DELETE", handler)
}

//Adds a handler for the 'GET' http method.
func Get(route string, handler interface{}) {
    mainServer.Get(route, handler)
    //addRoute(route, "GET", handler)
}

//Adds a handler for the 'POST' http method.
func Post(route string, handler interface{}) {
    mainServer.addRoute(route, "POST", handler)
}

//Adds a handler for the 'PUT' http method.
func Put(route string, handler interface{}) {
    mainServer.addRoute(route, "PUT", handler)
}

//Adds a handler for the 'DELETE' http method.
func Delete(route string, handler interface{}) {
    mainServer.addRoute(route, "DELETE", handler)
}

func (s *Server) SetLogger(logger *log.Logger) {
    s.Logger = logger
}

func SetLogger(logger *log.Logger) {
    mainServer.Logger = logger
}

type ServerConfig struct {
    StaticDir    string
    Addr         string
    Port         int
    CookieSecret string
    RecoverPanic bool
}

func webTime(t *time.Time) string {
    ftime := t.Format(time.RFC1123)
    if strings.HasSuffix(ftime, "UTC") {
        ftime = ftime[0:len(ftime)-3] + "GMT"
    }
    return ftime
}

func dirExists(dir string) bool {
    d, e := os.Stat(dir)
    switch {
    case e != nil:
        return false
    case !d.IsDirectory():
        return false
    }

    return true
}

func fileExists(dir string) bool {
    info, err := os.Stat(dir)
    if err != nil {
        return false
    } else if !info.IsRegular() {
        return false
    }

    return true
}

func Urlencode(data map[string]string) string {
    var buf bytes.Buffer
    for k, v := range data {
        buf.WriteString(http.URLEscape(k))
        buf.WriteByte('=')
        buf.WriteString(http.URLEscape(v))
        buf.WriteByte('&')
    }
    s := buf.String()
    return s[0 : len(s)-1]
}
<commit_msg>Fixed bug where SetCookie age parameter wasn't working.<commit_after>package web

import (
    "bytes"
    "container/vector"
    "crypto/hmac"
    "encoding/base64"
    "fmt"
    "http"
    "io/ioutil"
    "log"
    "os"
    "path"
    "reflect"
    "regexp"
    "runtime"
    "strconv"
    "strings"
    "time"
)

type conn interface {
    StartResponse(status int)
    SetHeader(hdr string, val string, unique bool)
    Write(data []byte) (n int, err os.Error)
    Close()
}

type Context struct {
    *Request
    *Server
    conn
    responseStarted bool
}

func (ctx *Context) StartResponse(status int) {
    ctx.conn.StartResponse(status)
    ctx.responseStarted = true
}

func (ctx *Context) Write(data []byte) (n int, err os.Error) {
    if !ctx.responseStarted {
        ctx.StartResponse(200)
    }

    //if it's a HEAD request, we just write blank data
    if ctx.Request.Method == "HEAD" {
        data = []byte{}
    }

    return ctx.conn.Write(data)
}
func (ctx *Context) WriteString(content string) {
    ctx.Write([]byte(content))
}

func (ctx *Context) Abort(status int, body string) {
    ctx.StartResponse(status)
    ctx.WriteString(body)
}

func (ctx *Context) Redirect(status int, url string) {
    ctx.SetHeader("Location", url, true)
    ctx.StartResponse(status)
    ctx.WriteString("Redirecting to: " + url)
}

func (ctx *Context) NotModified() {
    ctx.StartResponse(304)
}

func (ctx *Context) NotFound(message string) {
    ctx.StartResponse(404)
    ctx.WriteString(message)
}

//Sets a cookie -- duration is the amount of time in seconds. 0 = forever
func (ctx *Context) SetCookie(name string, value string, age int64) {
	var utctime *time.Time
	if age == 0 {
		// 2^31 - 1 seconds (roughly 2038)
		utctime = time.SecondsToUTC(2147483647)
	} else {
		utctime = time.SecondsToUTC(time.UTC().Seconds() + age)
	}
	cookie := fmt.Sprintf("%s=%s; expires=%s", name, value, webTime(utctime))
	ctx.SetHeader("Set-Cookie", cookie, false)
}

func getCookieSig(key string, val []byte, timestamp string) string {
    hm := hmac.NewSHA1([]byte(key))

    hm.Write(val)
    hm.Write([]byte(timestamp))

    hex := fmt.Sprintf("%02x", hm.Sum())
    return hex
}

func (ctx *Context) SetSecureCookie(name string, val string, age int64) {
    //base64 encode the val
    if len(ctx.Server.Config.CookieSecret) == 0 {
        ctx.Logger.Println("Secret Key for secure cookies has not been set. Please call web.SetCookieSecret")
        return
    }
    var buf bytes.Buffer
    encoder := base64.NewEncoder(base64.StdEncoding, &buf)
    encoder.Write([]byte(val))
    encoder.Close()
    vs := buf.String()
    vb := buf.Bytes()
    timestamp := strconv.Itoa64(time.Seconds())
    sig := getCookieSig(ctx.Server.Config.CookieSecret, vb, timestamp)
    cookie := strings.Join([]string{vs, timestamp, sig}, "|")
    ctx.SetCookie(name, cookie, age)
}

func (ctx *Context) GetSecureCookie(name string) (string, bool) {
    cookie, ok := ctx.Request.Cookies[name]
    if !ok {
        return "", false
    }

    parts := strings.Split(cookie, "|", 3)

    val := parts[0]
    timestamp := parts[1]
    sig := parts[2]

    if getCookieSig(ctx.Server.Config.CookieSecret, []byte(val), timestamp) != sig {
        return "", false
    }

    ts, _ := strconv.Atoi64(timestamp)

    if time.Seconds()-31*86400 > ts {
        return "", false
    }

    buf := bytes.NewBufferString(val)
    encoder := base64.NewDecoder(base64.StdEncoding, buf)

    res, _ := ioutil.ReadAll(encoder)
    return string(res), true
}

// small optimization: cache the context type instead of repeteadly calling reflect.Typeof
var contextType reflect.Type

var exeFile string

// default
func defaultStaticDir() string {
    root, _ := path.Split(exeFile)
    return path.Join(root, "static")
}

func init() {
    contextType = reflect.Typeof(Context{})
    //find the location of the exe file
    arg0 := path.Clean(os.Args[0])
    wd, _ := os.Getwd()
    if strings.HasPrefix(arg0, "/") {
        exeFile = arg0
    } else {
        //TODO for robustness, search each directory in $PATH
        exeFile = path.Join(wd, arg0)
    }
}

type route struct {
    r       string
    cr      *regexp.Regexp
    method  string
    handler *reflect.FuncValue
}

func (s *Server) addRoute(r string, method string, handler interface{}) {
    cr, err := regexp.Compile(r)
    if err != nil {
        s.Logger.Printf("Error in route regex %q\n", r)
        return
    }
    fv := reflect.NewValue(handler).(*reflect.FuncValue)
    s.routes.Push(route{r, cr, method, fv})
}

type httpConn struct {
    conn http.ResponseWriter
}

func (c *httpConn) StartResponse(status int) { c.conn.WriteHeader(status) }

func (c *httpConn) SetHeader(hdr string, val string, unique bool) {
    //right now unique can't be implemented through the http package.
    //see issue 488
    c.conn.SetHeader(hdr, val)
}

func (c *httpConn) WriteString(content string) {
    buf := bytes.NewBufferString(content)
    c.conn.Write(buf.Bytes())
}

func (c *httpConn) Write(content []byte) (n int, err os.Error) {
    return c.conn.Write(content)
}

func (c *httpConn) Close() {
    rwc, buf, _ := c.conn.Hijack()
    if buf != nil {
        buf.Flush()
    }

    if rwc != nil {
        rwc.Close()
    }
}

func (s *Server) ServeHTTP(c http.ResponseWriter, req *http.Request) {
    conn := httpConn{c}
    wreq := newRequest(req, c)
    s.routeHandler(wreq, &conn)
}

func (s *Server) safelyCall(function *reflect.FuncValue, args []reflect.Value) (resp []reflect.Value, e interface{}) {
    defer func() {
        if err := recover(); err != nil {
            if !s.Config.RecoverPanic {
                // go back to panic
                panic(err)
            } else {
                e = err
                resp = nil
                s.Logger.Println("Handler crashed with error", err)
                for i := 1; ; i += 1 {
                    _, file, line, ok := runtime.Caller(i)
                    if !ok {
                        break
                    }
                    s.Logger.Println(file, line)
                }
            }
        }
    }()
    return function.Call(args), nil
}

func (s *Server) routeHandler(req *Request, c conn) {
    requestPath := req.URL.Path

    //log the request
    if len(req.URL.RawQuery) == 0 {
        s.Logger.Println(req.Method + " " + requestPath)
    } else {
        s.Logger.Println(req.Method + " " + requestPath + "?" + req.URL.RawQuery)
    }

    //parse the form data (if it exists)
    perr := req.parseParams()
    if perr != nil {
        s.Logger.Printf("Failed to parse form data %q\n", perr.String())
    }

    //parse the cookies
    perr = req.parseCookies()
    if perr != nil {
        s.Logger.Printf("Failed to parse cookies %q", perr.String())
    }

    ctx := Context{req, s, c, false}

    //set some default headers
    ctx.SetHeader("Content-Type", "text/html; charset=utf-8", true)
    ctx.SetHeader("Server", "web.go", true)

    tm := time.UTC()
    ctx.SetHeader("Date", webTime(tm), true)

    //try to serve a static file
    staticDir := s.Config.StaticDir
    if staticDir == "" {
        staticDir = defaultStaticDir()
    }
    staticFile := path.Join(staticDir, requestPath)
    if fileExists(staticFile) && (req.Method == "GET" || req.Method == "HEAD") {
        serveFile(&ctx, staticFile)
        return
    }

    for i := 0; i < s.routes.Len(); i++ {
        route := s.routes.At(i).(route)
        cr := route.cr
        //if the methods don't match, skip this handler (except HEAD can be used in place of GET)
        if req.Method != route.method && !(req.Method == "HEAD" && route.method == "GET") {
            continue
        }

        if !cr.MatchString(requestPath) {
            continue
        }
        match := cr.FindStringSubmatch(requestPath)

        if len(match[0]) != len(requestPath) {
            continue
        }

        var args vector.Vector

        handlerType := route.handler.Type().(*reflect.FuncType)

        //check if the first arg in the handler is a context type
        if handlerType.NumIn() > 0 {
            if a0, ok := handlerType.In(0).(*reflect.PtrType); ok {
                typ := a0.Elem()
                if typ == contextType {
                    args.Push(reflect.NewValue(&ctx))
                }
            }
        }

        for _, arg := range match[1:] {
            args.Push(reflect.NewValue(arg))
        }

        if args.Len() != handlerType.NumIn() {
            s.Logger.Printf("Incorrect number of arguments for %s\n", requestPath)
            ctx.Abort(500, "Server Error")
            return
        }

        valArgs := make([]reflect.Value, args.Len())
        for i := 0; i < args.Len(); i++ {
            valArgs[i] = args.At(i).(reflect.Value)
        }

        ret, err := s.safelyCall(route.handler, valArgs)
        if err != nil {
            //there was a panic in the handler
            ctx.Abort(500, "Server Error")
        }

        if len(ret) == 0 {
            return
        }

        sval, ok := ret[0].(*reflect.StringValue)

        if ok && !ctx.responseStarted {
            content := []byte(sval.Get())
            ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
            ctx.StartResponse(200)
            ctx.Write(content)
        }

        return
    }

    //try to serve index.html || index.htm
    if indexPath := path.Join(path.Join(staticDir, requestPath), "index.html"); fileExists(indexPath) {
        serveFile(&ctx, indexPath)
        return
    }

    if indexPath := path.Join(path.Join(staticDir, requestPath), "index.htm"); fileExists(indexPath) {
        serveFile(&ctx, indexPath)
        return
    }

    ctx.Abort(404, "Page not found")
}

var Config = &ServerConfig{
    RecoverPanic: true,
}

var mainServer = Server{
    Config: Config,
    Logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
}

type Server struct {
    Config *ServerConfig
    routes vector.Vector
    Logger *log.Logger
}

func (s *Server) initServer() {
    if s.Config == nil {
        s.Config = &ServerConfig{}
    }

    if s.Logger == nil {
        s.Logger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
    }
}

func (s *Server) Run(addr string) {
    s.initServer()

    mux := http.NewServeMux()
    mux.Handle("/", s)
    s.Logger.Printf("web.go serving %s\n", addr)
    err := http.ListenAndServe(addr, mux)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}

//runs the web application and serves http requests
func Run(addr string) {
    mainServer.Run(addr)
}

func (s *Server) RunScgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving scgi %s\n", addr)
    s.listenAndServeScgi(addr)
}

//runs the web application and serves scgi requests
func RunScgi(addr string) {
    mainServer.RunScgi(addr)
}

func (s *Server) RunFcgi(addr string) {
    s.initServer()
    s.Logger.Printf("web.go serving fcgi %s\n", addr)
    s.listenAndServeFcgi(addr)
}

//runs the web application by serving fastcgi requests
func RunFcgi(addr string) {
    mainServer.RunFcgi(addr)
}

//Adds a handler for the 'GET' http method.
func (s *Server) Get(route string, handler interface{}) {
    s.addRoute(route, "GET", handler)
}

//Adds a handler for the 'POST' http method.
func (s *Server) Post(route string, handler interface{}) {
    s.addRoute(route, "POST", handler)
}

//Adds a handler for the 'PUT' http method.
func (s *Server) Put(route string, handler interface{}) {
    s.addRoute(route, "PUT", handler)
}

//Adds a handler for the 'DELETE' http method.
func (s *Server) Delete(route string, handler interface{}) {
    s.addRoute(route, "DELETE", handler)
}

//Adds a handler for the 'GET' http method.
func Get(route string, handler interface{}) {
    mainServer.Get(route, handler)
    //addRoute(route, "GET", handler)
}

//Adds a handler for the 'POST' http method.
func Post(route string, handler interface{}) {
    mainServer.addRoute(route, "POST", handler)
}

//Adds a handler for the 'PUT' http method.
func Put(route string, handler interface{}) {
    mainServer.addRoute(route, "PUT", handler)
}

//Adds a handler for the 'DELETE' http method.
func Delete(route string, handler interface{}) {
    mainServer.addRoute(route, "DELETE", handler)
}

func (s *Server) SetLogger(logger *log.Logger) {
    s.Logger = logger
}

func SetLogger(logger *log.Logger) {
    mainServer.Logger = logger
}

type ServerConfig struct {
    StaticDir    string
    Addr         string
    Port         int
    CookieSecret string
    RecoverPanic bool
}

func webTime(t *time.Time) string {
    ftime := t.Format(time.RFC1123)
    if strings.HasSuffix(ftime, "UTC") {
        ftime = ftime[0:len(ftime)-3] + "GMT"
    }
    return ftime
}

func dirExists(dir string) bool {
    d, e := os.Stat(dir)
    switch {
    case e != nil:
        return false
    case !d.IsDirectory():
        return false
    }

    return true
}

func fileExists(dir string) bool {
    info, err := os.Stat(dir)
    if err != nil {
        return false
    } else if !info.IsRegular() {
        return false
    }

    return true
}

func Urlencode(data map[string]string) string {
    var buf bytes.Buffer
    for k, v := range data {
        buf.WriteString(http.URLEscape(k))
        buf.WriteByte('=')
        buf.WriteString(http.URLEscape(v))
        buf.WriteByte('&')
    }
    s := buf.String()
    return s[0 : len(s)-1]
}
<|endoftext|>


<commit_before>// Package zfs provides wrappers around the ZFS command line tools
package zfs

import (
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
)

// Dataset is a zfs dataset.  This could be a volume, filesystem, snapshot. Check the type field
// The field definitions can be found in the zfs manual: http://www.freebsd.org/cgi/man.cgi?zfs(8)
type Dataset struct {
	Name          string
	Used          uint64
	Avail         uint64
	Mountpoint    string
	Compression   string
	Type          string
	Written       uint64
	Volsize       uint64
	Usedbydataset uint64
	Quota         uint64
}

// helper function to wrap typical calls to zfs
func zfs(arg ...string) ([][]string, error) {
	c := command{Command: "zfs"}
	return c.Run(arg...)
}

// Datasets returns a slice of all datasets
func Datasets(filter string) ([]*Dataset, error) {
	return listByType("all", filter)
}

// Snapshots returns a slice of all snapshots
func Snapshots(filter string) ([]*Dataset, error) {
	return listByType("snapshot", filter)
}

// Filesystems returns a slice of all filesystems
func Filesystems(filter string) ([]*Dataset, error) {
	return listByType("filesystem", filter)
}

// Volumes returns a slice of all volumes
func Volumes(filter string) ([]*Dataset, error) {
	return listByType("volume", filter)
}

// GetDataset retrieves a single dataset
func GetDataset(name string) (*Dataset, error) {
	out, err := zfs("list", "-Hpo", strings.Join(propertyFields, ","), name)
	if err != nil {
		return nil, err
	}
	return parseDatasetLine(out[0])
}

// Clone clones a snapshot. An error will be returned if a non-snapshot is used
func (d *Dataset) Clone(dest string, properties map[string]string) (*Dataset, error) {
	if d.Type != "snapshot" {
		return nil, errors.New("can only clone snapshots")
	}
	args := make([]string, 2, 4)
	args[0] = "clone"
	args[1] = "-p"
	if properties != nil {
		args = append(args, propsSlice(properties)...)
	}
	args = append(args, []string{d.Name, dest}...)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(dest)
}

// ReceiveSnapshot receives a zfs stream into a new snapshot
func ReceiveSnapshot(input io.Reader, name string) (*Dataset, error) {
	c := command{Command: "zfs", Stdin: input}
	_, err := c.Run("receive", name)
	if err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// CreateVolume creates a new volume
func CreateVolume(name string, size uint64, properties map[string]string) (*Dataset, error) {
	args := make([]string, 4, 5)
	args[0] = "create"
	args[1] = "-p"
	args[2] = "-V"
	args[3] = strconv.FormatUint(size, 10)
	if properties != nil {
		args = append(args, propsSlice(properties)...)
	}
	args = append(args, name)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// Destroy destroys a dataset
func (d *Dataset) Destroy(recursive bool) error {
	args := make([]string, 1, 3)
	args[0] = "destroy"
	if recursive {
		args = append(args, "-r")
	}
	args = append(args, d.Name)
	_, err := zfs(args...)
	return err
}

// SetProperty sets a property
func (d *Dataset) SetProperty(key, val string) error {
	prop := strings.Join([]string{key, val}, "=")
	_, err := zfs("set", prop, d.Name)
	return err
}

// GetProperty Gets a property
func (d *Dataset) GetProperty(key string) (string, error) {
	out, err := zfs("get", key, d.Name)
	if err != nil {
		return "", err
	}

	return out[0][2], nil
}

// Snapshots returns a slice of all snapshots of a given dataset
func (d *Dataset) Snapshots() ([]*Dataset, error) {
	return listByType("snapshot", d.Name)
}

// CreateFilesystem creates a new filesystem
func CreateFilesystem(name string, properties map[string]string) (*Dataset, error) {
	args := make([]string, 1, 4)
	args[0] = "create"

	if properties != nil {
		args = append(args, propsSlice(properties)...)
	}

	args = append(args, name)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// Snapshot creates a snapshot
func (d *Dataset) Snapshot(name string, properties map[string]string) (*Dataset, error) {
	args := make([]string, 1, 4)
	args[0] = "snapshot"
	if properties != nil {
		args = append(args, propsSlice(properties)...)
	}
	snapName := fmt.Sprintf("%s@%s", d.Name, name)
	args = append(args, snapName)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(snapName)
}

// Children returns the children of the dataset. Depth of 0 does not limit recursion.
func (d *Dataset) Children(depth uint64) ([]*Dataset, error) {
	args := []string{"list", "-t", "all", "-rHpo", strings.Join(propertyFields, ",")}[:]
	if depth > 0 {
		args = append(args, "-d")
		args = append(args, strconv.FormatUint(depth, 10))
	}
	args = append(args, d.Name)

	out, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	datasets, err := parseDatasetLines(out)
	if err != nil {
		return nil, err
	}
	// first element is the dataset itself
	return datasets[1:len(datasets)], nil
}
<commit_msg>MIST-150 Change Snapshot second paramater from properties map[string][string] to recursive bool<commit_after>// Package zfs provides wrappers around the ZFS command line tools
package zfs

import (
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
)

// Dataset is a zfs dataset.  This could be a volume, filesystem, snapshot. Check the type field
// The field definitions can be found in the zfs manual: http://www.freebsd.org/cgi/man.cgi?zfs(8)
type Dataset struct {
	Name          string
	Used          uint64
	Avail         uint64
	Mountpoint    string
	Compression   string
	Type          string
	Written       uint64
	Volsize       uint64
	Usedbydataset uint64
	Quota         uint64
}

// helper function to wrap typical calls to zfs
func zfs(arg ...string) ([][]string, error) {
	c := command{Command: "zfs"}
	return c.Run(arg...)
}

// Datasets returns a slice of all datasets
func Datasets(filter string) ([]*Dataset, error) {
	return listByType("all", filter)
}

// Snapshots returns a slice of all snapshots
func Snapshots(filter string) ([]*Dataset, error) {
	return listByType("snapshot", filter)
}

// Filesystems returns a slice of all filesystems
func Filesystems(filter string) ([]*Dataset, error) {
	return listByType("filesystem", filter)
}

// Volumes returns a slice of all volumes
func Volumes(filter string) ([]*Dataset, error) {
	return listByType("volume", filter)
}

// GetDataset retrieves a single dataset
func GetDataset(name string) (*Dataset, error) {
	out, err := zfs("list", "-Hpo", strings.Join(propertyFields, ","), name)
	if err != nil {
		return nil, err
	}
	return parseDatasetLine(out[0])
}

// Clone clones a snapshot. An error will be returned if a non-snapshot is used
func (d *Dataset) Clone(dest string, properties map[string]string) (*Dataset, error) {
	if d.Type != "snapshot" {
		return nil, errors.New("can only clone snapshots")
	}
	args := make([]string, 2, 4)
	args[0] = "clone"
	args[1] = "-p"
	if properties != nil {
		args = append(args, propsSlice(properties)...)
	}
	args = append(args, []string{d.Name, dest}...)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(dest)
}

// ReceiveSnapshot receives a zfs stream into a new snapshot
func ReceiveSnapshot(input io.Reader, name string) (*Dataset, error) {
	c := command{Command: "zfs", Stdin: input}
	_, err := c.Run("receive", name)
	if err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// CreateVolume creates a new volume
func CreateVolume(name string, size uint64, properties map[string]string) (*Dataset, error) {
	args := make([]string, 4, 5)
	args[0] = "create"
	args[1] = "-p"
	args[2] = "-V"
	args[3] = strconv.FormatUint(size, 10)
	if properties != nil {
		args = append(args, propsSlice(properties)...)
	}
	args = append(args, name)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// Destroy destroys a dataset
func (d *Dataset) Destroy(recursive bool) error {
	args := make([]string, 1, 3)
	args[0] = "destroy"
	if recursive {
		args = append(args, "-r")
	}
	args = append(args, d.Name)
	_, err := zfs(args...)
	return err
}

// SetProperty sets a property
func (d *Dataset) SetProperty(key, val string) error {
	prop := strings.Join([]string{key, val}, "=")
	_, err := zfs("set", prop, d.Name)
	return err
}

// GetProperty Gets a property
func (d *Dataset) GetProperty(key string) (string, error) {
	out, err := zfs("get", key, d.Name)
	if err != nil {
		return "", err
	}

	return out[0][2], nil
}

// Snapshots returns a slice of all snapshots of a given dataset
func (d *Dataset) Snapshots() ([]*Dataset, error) {
	return listByType("snapshot", d.Name)
}

// CreateFilesystem creates a new filesystem
func CreateFilesystem(name string, properties map[string]string) (*Dataset, error) {
	args := make([]string, 1, 4)
	args[0] = "create"

	if properties != nil {
		args = append(args, propsSlice(properties)...)
	}

	args = append(args, name)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(name)
}

// Snapshot creates a snapshot
func (d *Dataset) Snapshot(name string, recursive bool) (*Dataset, error) {
	args := make([]string, 1, 4)
	args[0] = "snapshot"
	if recursive {
		args = append(args, "-r")
	}
	snapName := fmt.Sprintf("%s@%s", d.Name, name)
	args = append(args, snapName)
	_, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	return GetDataset(snapName)
}

// Children returns the children of the dataset. Depth of 0 does not limit recursion.
func (d *Dataset) Children(depth uint64) ([]*Dataset, error) {
	args := []string{"list", "-t", "all", "-rHpo", strings.Join(propertyFields, ",")}[:]
	if depth > 0 {
		args = append(args, "-d")
		args = append(args, strconv.FormatUint(depth, 10))
	}
	args = append(args, d.Name)

	out, err := zfs(args...)
	if err != nil {
		return nil, err
	}
	datasets, err := parseDatasetLines(out)
	if err != nil {
		return nil, err
	}
	// first element is the dataset itself
	return datasets[1:len(datasets)], nil
}
<|endoftext|>


<commit_before>package zim

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"
	"unicode/utf8"
)

const (
	ZIM = 72173914
)

type ZimReader struct {
	f             *os.File
	ArticleCount  uint32
	clusterCount  uint32
	urlPtrPos     uint64
	titlePtrPos   uint64
	clusterPtrPos uint64
	mimeListPos   uint64
	mainPage      uint32
	layoutPage    uint32
}

func NewReader(path string) (*ZimReader, error) {
	f, err := os.Open(path)
	z := ZimReader{f: f, mainPage: 0xffffffff, layoutPage: 0xffffffff}
	if err != nil {
		return nil, err
	}

	err = z.readFileHeaders()
	return &z, err
}

func (z *ZimReader) readFileHeaders() error {
	// get size
	z.f.Stat()
	_, err := z.f.Seek(0, 0)
	if err != nil {
		panic(err)
	}

	b := make([]byte, 1024)

	_, err = z.f.Read(b)
	if err != nil {
		panic(err)
	}

	// checking for file type
	v, err := readInt32(b[0:4])
	if err != nil {
		panic(err)
	}
	if v != ZIM {
		return errors.New("Not a ZIM file")
	}

	// checking for version
	v, err = readInt32(b[4:9])
	if err != nil {
		panic(err)
	}
	if v != 5 {
		return errors.New("Unsupported version 5 only")
	}

	// checking for articles count
	v, err = readInt32(b[24:29])
	if err != nil {
		panic(err)
	}
	z.ArticleCount = v

	// checking for cluster count
	v, err = readInt32(b[28:33])
	if err != nil {
		panic(err)
	}
	z.clusterCount = v

	// checking for urlPtrPos
	vb, err := readInt64(b[32:41])
	if err != nil {
		panic(err)
	}
	z.urlPtrPos = vb

	// checking for titlePtrPos
	vb, err = readInt64(b[40:49])
	if err != nil {
		panic(err)
	}
	z.titlePtrPos = vb

	// checking for clusterPtrPos
	vb, err = readInt64(b[48:57])
	if err != nil {
		panic(err)
	}
	z.clusterPtrPos = vb

	// checking for mimeListPos
	vb, err = readInt64(b[56:65])
	if err != nil {
		panic(err)
	}
	z.mimeListPos = vb

	// checking for mainPage
	v, err = readInt32(b[64:69])
	if err != nil {
		panic(err)
	}
	z.mainPage = v

	// checking for layoutPage
	v, err = readInt32(b[68:73])
	if err != nil {
		panic(err)
	}
	z.layoutPage = v

	// Mime type list
	z.f.Seek(int64(z.mimeListPos), 0)

	eos := make([]byte, 1)
	utf8.EncodeRune(eos, '\x00')
	for {
		// read a chunk
		n, err := z.f.Read(b)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if n == 0 {
			break
		}

		a := bytes.Split(b, eos)
		fmt.Println(len(a))
		for m := range a {
			fmt.Println(string(m))
		}
		break

	}

	return err
}

func readInt32(b []byte) (v uint32, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func readInt64(b []byte) (v uint64, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func (z *ZimReader) FindByName(string) error {
	return nil
}
<commit_msg>read 1st mime<commit_after>package zim

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"
)

const (
	ZIM = 72173914
)

type ZimReader struct {
	f             *os.File
	ArticleCount  uint32
	clusterCount  uint32
	urlPtrPos     uint64
	titlePtrPos   uint64
	clusterPtrPos uint64
	mimeListPos   uint64
	mainPage      uint32
	layoutPage    uint32
}

func NewReader(path string) (*ZimReader, error) {
	f, err := os.Open(path)
	z := ZimReader{f: f, mainPage: 0xffffffff, layoutPage: 0xffffffff}
	if err != nil {
		return nil, err
	}

	err = z.readFileHeaders()
	return &z, err
}

func (z *ZimReader) readFileHeaders() error {
	// get size
	z.f.Stat()
	_, err := z.f.Seek(0, 0)
	if err != nil {
		panic(err)
	}

	b := make([]byte, 1024)

	_, err = z.f.Read(b)
	if err != nil {
		panic(err)
	}

	// checking for file type
	v, err := readInt32(b[0:4])
	if err != nil {
		panic(err)
	}
	if v != ZIM {
		return errors.New("Not a ZIM file")
	}

	// checking for version
	v, err = readInt32(b[4:9])
	if err != nil {
		panic(err)
	}
	if v != 5 {
		return errors.New("Unsupported version 5 only")
	}

	// checking for articles count
	v, err = readInt32(b[24:29])
	if err != nil {
		panic(err)
	}
	z.ArticleCount = v

	// checking for cluster count
	v, err = readInt32(b[28:33])
	if err != nil {
		panic(err)
	}
	z.clusterCount = v

	// checking for urlPtrPos
	vb, err := readInt64(b[32:41])
	if err != nil {
		panic(err)
	}
	z.urlPtrPos = vb

	// checking for titlePtrPos
	vb, err = readInt64(b[40:49])
	if err != nil {
		panic(err)
	}
	z.titlePtrPos = vb

	// checking for clusterPtrPos
	vb, err = readInt64(b[48:57])
	if err != nil {
		panic(err)
	}
	z.clusterPtrPos = vb

	// checking for mimeListPos
	vb, err = readInt64(b[56:65])
	if err != nil {
		panic(err)
	}
	z.mimeListPos = vb

	// checking for mainPage
	v, err = readInt32(b[64:69])
	if err != nil {
		panic(err)
	}
	z.mainPage = v

	// checking for layoutPage
	v, err = readInt32(b[68:73])
	if err != nil {
		panic(err)
	}
	z.layoutPage = v

	// Mime type list
	z.f.Seek(int64(z.mimeListPos), 0)

	r := bufio.NewReader(z.f)
	for {
		line, err := r.ReadBytes('\x00')
		if err != nil && err != io.EOF {
			panic(err)
		}
		if string(line) == "" {
			break
		}
		fmt.Println(string(line))
		break

	}

	return err
}

func readInt32(b []byte) (v uint32, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func readInt64(b []byte) (v uint64, err error) {
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &v)
	if err != nil {
		return v, err
	}
	return v, nil
}

func (z *ZimReader) FindByName(string) error {
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"compress/gzip"
	"compress/zlib"
	"flag"
	"fmt"
	"io/ioutil"
	"time"

	"github.com/golang/snappy"
)

func main() {

	var filepath *string = flag.String("f", "", "File to compress")
	flag.Parse()

	if *filepath == "" {
		fmt.Println("Error: Missing Flag -f filepath (required)")
		return
	}

	// Create a file (json or otherwise) using your own data to see how it
	// compresses with these different compression algorithms.
	dat, _ := ioutil.ReadFile(*filepath)

	orig := len(dat)

	fmt.Printf("Original: %d bytes\n", orig)

	var c []byte

	start := time.Now()
	for i := 0; i < 1e2; i++ {
		c = compressZlib(dat)
	}
	fmt.Printf("zlib compress %v %d bytes (%.1fx)\n", time.Since(start), len(c), float64(orig)/float64(len(c)))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		expandZlib(c)
	}
	fmt.Printf("zlib expand %v\n", time.Since(start))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		c = compressGzip(dat)
	}
	fmt.Printf("gzip compress %v %d bytes (%.1fx)\n", time.Since(start), len(c), float64(orig)/float64(len(c)))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		expandGzip(c)
	}
	fmt.Printf("gzip expand %v\n", time.Since(start))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		c = compressSnappy(dat)
	}
	fmt.Printf("snappy compress %v %d bytes (%.1fx)\n", time.Since(start), len(c), float64(orig)/float64(len(c)))

	res := []byte{}

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		res = expandSnappy(c)
	}
	fmt.Printf("snappy expand %v\n", time.Since(start))

}

func compressZlib(in []byte) []byte {
	var b bytes.Buffer
	w := zlib.NewWriter(&b)
	w.Write(in)
	w.Close()
	return b.Bytes()
}

func expandZlib(in []byte) []byte {
	buf := bytes.NewBuffer(in)
	r, _ := zlib.NewReader(buf)
	out, _ := ioutil.ReadAll(r)
	r.Close()
	return out
}

func compressGzip(in []byte) []byte {
	var b bytes.Buffer
	w := gzip.NewWriter(&b)
	w.Write(in)
	w.Close()
	return b.Bytes()
}

func expandGzip(in []byte) []byte {
	buf := bytes.NewBuffer(in)
	r, _ := gzip.NewReader(buf)
	out, _ := ioutil.ReadAll(r)
	r.Close()
	return out
}

func compressSnappy(in []byte) []byte {
	return snappy.Encode(nil, in)
}

func expandSnappy(in []byte) []byte {
	out, _ := snappy.Decode(nil, in)
	return out
}
<commit_msg>Fixes failed test in tool compare_compression<commit_after>package main

import (
	"bytes"
	"compress/gzip"
	"compress/zlib"
	"flag"
	"fmt"
	"io/ioutil"
	"time"

	"github.com/golang/snappy"
)

func main() {

	var filepath *string = flag.String("f", "", "File to compress")
	flag.Parse()

	if *filepath == "" {
		fmt.Println("Error: Missing Flag -f filepath (required)")
		return
	}

	// Create a file (json or otherwise) using your own data to see how it
	// compresses with these different compression algorithms.
	dat, _ := ioutil.ReadFile(*filepath)

	orig := len(dat)

	fmt.Printf("Original: %d bytes\n", orig)

	var c []byte

	start := time.Now()
	for i := 0; i < 1e2; i++ {
		c = compressZlib(dat)
	}
	fmt.Printf("zlib compress %v %d bytes (%.1fx)\n", time.Since(start), len(c), float64(orig)/float64(len(c)))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		expandZlib(c)
	}
	fmt.Printf("zlib expand %v\n", time.Since(start))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		c = compressGzip(dat)
	}
	fmt.Printf("gzip compress %v %d bytes (%.1fx)\n", time.Since(start), len(c), float64(orig)/float64(len(c)))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		expandGzip(c)
	}
	fmt.Printf("gzip expand %v\n", time.Since(start))

	start = time.Now()
	for i := 0; i < 1e2; i++ {
		c = compressSnappy(dat)
	}
	fmt.Printf("snappy compress %v %d bytes (%.1fx)\n", time.Since(start), len(c), float64(orig)/float64(len(c)))

	func(res []byte) []byte {
		start = time.Now()
		for i := 0; i < 1e2; i++ {
			res = expandSnappy(c)
		}
		return res
	}([]byte{})
	fmt.Printf("snappy expand %v\n", time.Since(start))

}

func compressZlib(in []byte) []byte {
	var b bytes.Buffer
	w := zlib.NewWriter(&b)
	w.Write(in)
	w.Close()
	return b.Bytes()
}

func expandZlib(in []byte) []byte {
	buf := bytes.NewBuffer(in)
	r, _ := zlib.NewReader(buf)
	out, _ := ioutil.ReadAll(r)
	r.Close()
	return out
}

func compressGzip(in []byte) []byte {
	var b bytes.Buffer
	w := gzip.NewWriter(&b)
	w.Write(in)
	w.Close()
	return b.Bytes()
}

func expandGzip(in []byte) []byte {
	buf := bytes.NewBuffer(in)
	r, _ := gzip.NewReader(buf)
	out, _ := ioutil.ReadAll(r)
	r.Close()
	return out
}

func compressSnappy(in []byte) []byte {
	return snappy.Encode(nil, in)
}

func expandSnappy(in []byte) []byte {
	out, _ := snappy.Decode(nil, in)
	return out
}
<|endoftext|>


<commit_before>/*
   Copyright 2015 Comcast Cable Communications Management, LLC

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

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

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

package client

// GetDeliveryServiceResponse ...
type GetDeliveryServiceResponse struct {
	Response []DeliveryService `json:"response"`
}

// CreateDeliveryServiceResponse ...
type CreateDeliveryServiceResponse struct {
	Response []DeliveryService      `json:"response"`
	Alerts   []DeliveryServiceAlert `json:"alerts"`
}

// DeliveryServiceResponse ...
type DeliveryServiceResponse struct {
	Response DeliveryService        `json:"response"`
	Alerts   []DeliveryServiceAlert `json:"alerts"`
}

// DeleteDeliveryServiceResponse ...
type DeleteDeliveryServiceResponse struct {
	Alerts []DeliveryServiceAlert `json:"alerts"`
}

// DeliveryService ...
type DeliveryService struct {
	ID                   int                    `json:"id"`
	XMLID                string                 `json:"xmlId"`
	Active               bool                   `json:"active"`
	DSCP                 int                    `json:"dscp"`
	Signed               bool                   `json:"signed"`
	QStringIgnore        int                    `json:"qstringIgnore"`
	GeoLimit             int                    `json:"geoLimit"`
	GeoProvider          int                    `json:"geoProvider"`
	HTTPBypassFQDN       string                 `json:"httpBypassFqdn"`
	DNSBypassIP          string                 `json:"dnsBypassIp"`
	DNSBypassIP6         string                 `json:"dnsBypassIp6"`
	DNSBypassCname       string                 `json:"dnsBypassCname"`
	DNSBypassTTL         int                    `json:"dnsBypassTtl"`
	OrgServerFQDN        string                 `json:"orgServerFqdn"`
	Type                 string                 `json:"type"`
	ProfileName          string                 `json:"profileName"`
	ProfileDesc          string                 `json:"profileDescription"`
	CDNName              string                 `json:"cdnName"`
	CCRDNSTTL            int                    `json:"ccrDnsTtl"`
	GlobalMaxMBPS        int                    `json:"globalMaxMbps"`
	GlobalMaxTPS         int                    `json:"globalMaxTps"`
	LongDesc             string                 `json:"longDesc"`
	LongDesc1            string                 `json:"longDesc1"`
	LongDesc2            string                 `json:"longDesc2"`
	MaxDNSAnswers        int                    `json:"maxDnsAnswers"`
	InfoURL              string                 `json:"infoUrl"`
	MissLat              float64                `json:"missLat"`
	MissLong             float64                `json:"missLong"`
	CheckPath            string                 `json:"checkPath"`
	LastUpdated          string                 `json:"lastUpdated"`
	Protocol             int                    `json:"protocol"`
	IPV6RoutingEnabled   bool                   `json:"ipv6RoutingEnabled"`
	RangeRequestHandling int                    `json:"rangeRequestHandling"`
	EdgeHeaderRewrite    string                 `json:"edgeHeaderRewrite"`
	MidHeaderRewrite     string                 `json:"midHeaderRewrite"`
	TRResponseHeaders    string                 `json:"trResponseHeaders"`
	RegexRemap           string                 `json:"regexRemap"`
	CacheURL             string                 `json:"cacheurl"`
	RemapText            string                 `json:"remapText"`
	MultiSiteOrigin      bool                   `json:"multiSiteOrigin"`
	DisplayName          string                 `json:"displayName"`
	InitialDispersion    int                    `json:"initialDispersion"`
	MatchList            []DeliveryServiceMatch `json:"matchList,omitempty"`
	RegionalGeoBlocking  bool                   `json:"regionalGeoBlocking"`
	LogsEnabled          bool                   `json:"logsEnabled"`
}

// DeliveryServiceMatch ...
type DeliveryServiceMatch struct {
	Type      string `json:"type"`
	SetNumber string `json:"setNumber"`
	Pattern   string `json:"pattern"`
}

// DeliveryServiceAlert ...
type DeliveryServiceAlert struct {
	Level string `json:"level"`
	Text  string `json:"text"`
}

// DeliveryServiceStateResponse ...
type DeliveryServiceStateResponse struct {
	Response DeliveryServiceState `json:"response"`
}

// DeliveryServiceState ...
type DeliveryServiceState struct {
	Enabled  bool                    `json:"enabled"`
	Failover DeliveryServiceFailover `json:"failover"`
}

// DeliveryServiceFailover ...
type DeliveryServiceFailover struct {
	Locations   []string                   `json:"locations"`
	Destination DeliveryServiceDestination `json:"destination"`
	Configured  bool                       `json:"configured"`
	Enabled     bool                       `json:"enabled"`
}

// DeliveryServiceDestination ...
type DeliveryServiceDestination struct {
	Location string `json:"location"`
	Type     string `json:"type"`
}

// DeliveryServiceHealthResponse ...
type DeliveryServiceHealthResponse struct {
	Response DeliveryServiceHealth `json:"response"`
}

// DeliveryServiceHealth ...
type DeliveryServiceHealth struct {
	TotalOnline  int                         `json:"totalOnline"`
	TotalOffline int                         `json:"totalOffline"`
	CacheGroups  []DeliveryServiceCacheGroup `json:"cacheGroups"`
}

// DeliveryServiceCacheGroup ...
type DeliveryServiceCacheGroup struct {
	Online  int    `json:"online"`
	Offline int    `json:"offline"`
	Name    string `json:"name"`
}

// DeliveryServiceCapacityResponse ...
type DeliveryServiceCapacityResponse struct {
	Response DeliveryServiceCapacity `json:"response"`
}

// DeliveryServiceCapacity ...
type DeliveryServiceCapacity struct {
	AvailablePercent   float64 `json:"availablePercent"`
	UnavailablePercent float64 `json:"unavailablePercent"`
	UtilizedPercent    float64 `json:"utilizedPercent"`
	MaintenancePercent float64 `json:"maintenancePercent"`
}

// DeliveryServiceRoutingResponse ...
type DeliveryServiceRoutingResponse struct {
	Response DeliveryServiceRouting `json:"response"`
}

// DeliveryServiceRouting ...
type DeliveryServiceRouting struct {
	StaticRoute       int     `json:"staticRoute"`
	Miss              int     `json:"miss"`
	Geo               float64 `json:"geo"`
	Err               int     `json:"err"`
	CZ                float64 `json:"cz"`
	DSR               float64 `json:"dsr"`
	Fed               int     `json:"fed"`
	RegionalAlternate int     `json:"regionalAlternate"`
	RegionalDenied    int     `json:"regionalDenied"`
}

// DeliveryServiceServerResponse ...
type DeliveryServiceServerResponse struct {
	Response []DeliveryServiceServer `json:"response"`
	Page     int                     `json:"page"`
	OrderBy  string                  `json:"orderby"`
	Limit    int                     `json:"limit"`
}

// DeliveryServiceServer ...
type DeliveryServiceServer struct {
	LastUpdated     string `json:"lastUpdated"`
	Server          string `json:"server"`
	DeliveryService string `json:"deliveryService"`
}

// DeliveryServiceSSLKeysResponse ...
type DeliveryServiceSSLKeysResponse struct {
	Response DeliveryServiceSSLKeys `json:"response"`
}

// DeliveryServiceSSLKeys ...
type DeliveryServiceSSLKeys struct {
	CDN             string                            `json:"cdn"`
	DeliveryService string                            `json:"DeliveryService"`
	BusinessUnit    string                            `json:"businessUnit"`
	City            string                            `json:"city"`
	Organization    string                            `json:"organization"`
	Hostname        string                            `json:"hostname"`
	Country         string                            `json:"country"`
	State           string                            `json:"state"`
	Version         string                            `json:"version"`
	Certificate     DeliveryServiceSSLKeysCertificate `json:"certificate"`
}

// DeliveryServiceSSLKeysCertificate ...
type DeliveryServiceSSLKeysCertificate struct {
	Crt string `json:"crt"`
	Key string `json:"key"`
	CSR string `json:"csr"`
}
<commit_msg>add cdnId, typeId, and profileId to DeliveryService struct<commit_after>/*
   Copyright 2015 Comcast Cable Communications Management, LLC

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

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

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

package client

// GetDeliveryServiceResponse ...
type GetDeliveryServiceResponse struct {
	Response []DeliveryService `json:"response"`
}

// CreateDeliveryServiceResponse ...
type CreateDeliveryServiceResponse struct {
	Response []DeliveryService      `json:"response"`
	Alerts   []DeliveryServiceAlert `json:"alerts"`
}

// DeliveryServiceResponse ...
type DeliveryServiceResponse struct {
	Response DeliveryService        `json:"response"`
	Alerts   []DeliveryServiceAlert `json:"alerts"`
}

// DeleteDeliveryServiceResponse ...
type DeleteDeliveryServiceResponse struct {
	Alerts []DeliveryServiceAlert `json:"alerts"`
}

// DeliveryService ...
type DeliveryService struct {
	ID                   int                    `json:"id"`
	XMLID                string                 `json:"xmlId"`
	Active               bool                   `json:"active"`
	DSCP                 int                    `json:"dscp"`
	Signed               bool                   `json:"signed"`
	QStringIgnore        int                    `json:"qstringIgnore"`
	GeoLimit             int                    `json:"geoLimit"`
	GeoProvider          int                    `json:"geoProvider"`
	HTTPBypassFQDN       string                 `json:"httpBypassFqdn"`
	DNSBypassIP          string                 `json:"dnsBypassIp"`
	DNSBypassIP6         string                 `json:"dnsBypassIp6"`
	DNSBypassCname       string                 `json:"dnsBypassCname"`
	DNSBypassTTL         int                    `json:"dnsBypassTtl"`
	OrgServerFQDN        string                 `json:"orgServerFqdn"`
	TypeID               int                    `json:"typeId"`
	Type                 string                 `json:"type"`
	ProfileID            int                    `json:"profileId"`
	ProfileName          string                 `json:"profileName"`
	ProfileDesc          string                 `json:"profileDescription"`
	CDNName              string                 `json:"cdnName"`
	CDNID                int                    `json:"cdnId"`
	CCRDNSTTL            int                    `json:"ccrDnsTtl"`
	GlobalMaxMBPS        int                    `json:"globalMaxMbps"`
	GlobalMaxTPS         int                    `json:"globalMaxTps"`
	LongDesc             string                 `json:"longDesc"`
	LongDesc1            string                 `json:"longDesc1"`
	LongDesc2            string                 `json:"longDesc2"`
	MaxDNSAnswers        int                    `json:"maxDnsAnswers"`
	InfoURL              string                 `json:"infoUrl"`
	MissLat              float64                `json:"missLat"`
	MissLong             float64                `json:"missLong"`
	CheckPath            string                 `json:"checkPath"`
	LastUpdated          string                 `json:"lastUpdated"`
	Protocol             int                    `json:"protocol"`
	IPV6RoutingEnabled   bool                   `json:"ipv6RoutingEnabled"`
	RangeRequestHandling int                    `json:"rangeRequestHandling"`
	EdgeHeaderRewrite    string                 `json:"edgeHeaderRewrite"`
	MidHeaderRewrite     string                 `json:"midHeaderRewrite"`
	TRResponseHeaders    string                 `json:"trResponseHeaders"`
	RegexRemap           string                 `json:"regexRemap"`
	CacheURL             string                 `json:"cacheurl"`
	RemapText            string                 `json:"remapText"`
	MultiSiteOrigin      bool                   `json:"multiSiteOrigin"`
	DisplayName          string                 `json:"displayName"`
	InitialDispersion    int                    `json:"initialDispersion"`
	MatchList            []DeliveryServiceMatch `json:"matchList,omitempty"`
	RegionalGeoBlocking  bool                   `json:"regionalGeoBlocking"`
	LogsEnabled          bool                   `json:"logsEnabled"`
}

// DeliveryServiceMatch ...
type DeliveryServiceMatch struct {
	Type      string `json:"type"`
	SetNumber string `json:"setNumber"`
	Pattern   string `json:"pattern"`
}

// DeliveryServiceAlert ...
type DeliveryServiceAlert struct {
	Level string `json:"level"`
	Text  string `json:"text"`
}

// DeliveryServiceStateResponse ...
type DeliveryServiceStateResponse struct {
	Response DeliveryServiceState `json:"response"`
}

// DeliveryServiceState ...
type DeliveryServiceState struct {
	Enabled  bool                    `json:"enabled"`
	Failover DeliveryServiceFailover `json:"failover"`
}

// DeliveryServiceFailover ...
type DeliveryServiceFailover struct {
	Locations   []string                   `json:"locations"`
	Destination DeliveryServiceDestination `json:"destination"`
	Configured  bool                       `json:"configured"`
	Enabled     bool                       `json:"enabled"`
}

// DeliveryServiceDestination ...
type DeliveryServiceDestination struct {
	Location string `json:"location"`
	Type     string `json:"type"`
}

// DeliveryServiceHealthResponse ...
type DeliveryServiceHealthResponse struct {
	Response DeliveryServiceHealth `json:"response"`
}

// DeliveryServiceHealth ...
type DeliveryServiceHealth struct {
	TotalOnline  int                         `json:"totalOnline"`
	TotalOffline int                         `json:"totalOffline"`
	CacheGroups  []DeliveryServiceCacheGroup `json:"cacheGroups"`
}

// DeliveryServiceCacheGroup ...
type DeliveryServiceCacheGroup struct {
	Online  int    `json:"online"`
	Offline int    `json:"offline"`
	Name    string `json:"name"`
}

// DeliveryServiceCapacityResponse ...
type DeliveryServiceCapacityResponse struct {
	Response DeliveryServiceCapacity `json:"response"`
}

// DeliveryServiceCapacity ...
type DeliveryServiceCapacity struct {
	AvailablePercent   float64 `json:"availablePercent"`
	UnavailablePercent float64 `json:"unavailablePercent"`
	UtilizedPercent    float64 `json:"utilizedPercent"`
	MaintenancePercent float64 `json:"maintenancePercent"`
}

// DeliveryServiceRoutingResponse ...
type DeliveryServiceRoutingResponse struct {
	Response DeliveryServiceRouting `json:"response"`
}

// DeliveryServiceRouting ...
type DeliveryServiceRouting struct {
	StaticRoute       int     `json:"staticRoute"`
	Miss              int     `json:"miss"`
	Geo               float64 `json:"geo"`
	Err               int     `json:"err"`
	CZ                float64 `json:"cz"`
	DSR               float64 `json:"dsr"`
	Fed               int     `json:"fed"`
	RegionalAlternate int     `json:"regionalAlternate"`
	RegionalDenied    int     `json:"regionalDenied"`
}

// DeliveryServiceServerResponse ...
type DeliveryServiceServerResponse struct {
	Response []DeliveryServiceServer `json:"response"`
	Page     int                     `json:"page"`
	OrderBy  string                  `json:"orderby"`
	Limit    int                     `json:"limit"`
}

// DeliveryServiceServer ...
type DeliveryServiceServer struct {
	LastUpdated     string `json:"lastUpdated"`
	Server          string `json:"server"`
	DeliveryService string `json:"deliveryService"`
}

// DeliveryServiceSSLKeysResponse ...
type DeliveryServiceSSLKeysResponse struct {
	Response DeliveryServiceSSLKeys `json:"response"`
}

// DeliveryServiceSSLKeys ...
type DeliveryServiceSSLKeys struct {
	CDN             string                            `json:"cdn"`
	DeliveryService string                            `json:"DeliveryService"`
	BusinessUnit    string                            `json:"businessUnit"`
	City            string                            `json:"city"`
	Organization    string                            `json:"organization"`
	Hostname        string                            `json:"hostname"`
	Country         string                            `json:"country"`
	State           string                            `json:"state"`
	Version         string                            `json:"version"`
	Certificate     DeliveryServiceSSLKeysCertificate `json:"certificate"`
}

// DeliveryServiceSSLKeysCertificate ...
type DeliveryServiceSSLKeysCertificate struct {
	Crt string `json:"crt"`
	Key string `json:"key"`
	CSR string `json:"csr"`
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"time"

	"github.com/streadway/amqp"
)

// AmqpBase is the base class for amqp senders and recievers, containing the
// startup and shutdown logic.
type AmqpBase struct {
	connection *amqp.Connection
	channel    *amqp.Channel
}

// Close the connection to amqp gracefully. Subclasses should ensure they finish
// all in-flight processing.
func (a AmqpBase) Close() {
	a.channel.Close()
	a.connection.Close()
}

// Connect to AMQP, and open a communication channel.
func (a *AmqpBase) dial(amqpURL string) (err error) {
	a.connection, err = amqp.Dial(amqpURL)
	if err != nil {
		log.Println("Could not connect to AMQP: ", err)
		return
	}

	a.channel, err = a.connection.Channel()
	if err != nil {
		log.Println("Could not open AMQP channel: ", err)
		return
	}

	return
}

// AmqpReceiver receives delayd commands over amqp
type AmqpReceiver struct {
	AmqpBase
	C    <-chan EntryWrapper
	rawC <-chan amqp.Delivery
}

// NewAmqpReceiver creates a new AmqpReceiver based on the provided AmqpConfig,
// and starts it listening for commands.
func NewAmqpReceiver(ac AmqpConfig) (receiver *AmqpReceiver, err error) {
	receiver = new(AmqpReceiver)

	err = receiver.dial(ac.URL)
	if err != nil {
		return
	}

	err = receiver.channel.ExchangeDeclare(ac.Exchange.Name, ac.Exchange.Kind, ac.Exchange.Durable, ac.Exchange.AutoDelete, ac.Exchange.Internal, ac.Exchange.NoWait, nil)
	if err != nil {
		log.Println("Could not declare AMQP Exchange: ", err)
		return
	}

	queue, err := receiver.channel.QueueDeclare(ac.Queue.Name, ac.Queue.Durable, ac.Queue.AutoDelete, ac.Queue.Exclusive, ac.Queue.NoWait, nil)
	if err != nil {
		log.Println("Could not declare AMQP Queue: ", err)
		return
	}

	for _, exch := range ac.Queue.Bind {
		log.Printf("Binding queue %s to exchange %s", queue.Name, exch)
		err = receiver.channel.QueueBind(queue.Name, "delayd", exch, ac.Queue.NoWait, nil)
		if err != nil {
			log.Fatalf("Error binding queue %s to Exchange %s", queue.Name, exch)
		}
	}

	//XXX set Qos here to match incoming concurrency
	messages, err := receiver.channel.Consume(queue.Name, "delayd", ac.Queue.AutoAck, ac.Queue.Exclusive, ac.Queue.NoLocal, ac.Queue.NoWait, nil)
	if err != nil {
		log.Println("Could not set up queue consume: ", err)
		return
	}

	c := make(chan EntryWrapper)
	receiver.C = c

	go func() {
		for {
			msg, ok := <-messages
			entry := Entry{}

			eWrapper := EntryWrapper{Msg: msg}

			// channel was closed. exit
			if !ok {
				return
			}

			delay, ok := msg.Headers["delayd-delay"].(int64)
			if !ok {
				log.Println("Bad/missing delay. discarding message")
				eWrapper.Done(false)
				continue
			}
			entry.SendAt = time.Now().Add(time.Duration(delay) * time.Millisecond)

			entry.Target, ok = msg.Headers["delayd-target"].(string)
			if !ok {
				log.Println("Bad/missing target. discarding message")
				eWrapper.Done(false)
				continue
			}

			// optional key value for overwrite
			h, ok := msg.Headers["delayd-key"].(string)
			if ok {
				entry.Key = h
			}

			// optional headers that will be relayed
			h, ok = msg.Headers["content-type"].(string)
			if ok {
				entry.ContentType = h
			}

			h, ok = msg.Headers["content-encoding"].(string)
			if ok {
				entry.ContentEncoding = h
			}

			h, ok = msg.Headers["correlation-id"].(string)
			if ok {
				entry.CorrelationID = h
			}

			entry.Body = msg.Body
			eWrapper.Entry = entry

			c <- eWrapper
		}
	}()

	return
}

// AmqpSender sends delayd entries over amqp after their timeout
type AmqpSender struct {
	AmqpBase

	C chan<- EntryWrapper
}

// NewAmqpSender creates a new AmqpSender connected to the given AMQP URL.
func NewAmqpSender(amqpURL string) (sender *AmqpSender, err error) {
	sender = new(AmqpSender)

	err = sender.dial(amqpURL)
	if err != nil {
		return
	}

	// XXX take exchange options from config?
	// XXX declare exchange too?

	return
}

// Send sends a delayd entry over AMQP, using the entry's Target as the publish
// exchange.
func (s AmqpSender) Send(e Entry) (err error) {
	msg := amqp.Publishing{
		DeliveryMode:    amqp.Persistent,
		Timestamp:       time.Now(),
		ContentType:     e.ContentType,
		ContentEncoding: e.ContentEncoding,
		CorrelationId:   e.CorrelationID,
		Body:            e.Body,
	}

	err = s.channel.Publish(e.Target, "", true, false, msg)
	return
}
<commit_msg>Remove more XXX's that are done<commit_after>package main

import (
	"log"
	"time"

	"github.com/streadway/amqp"
)

// AmqpBase is the base class for amqp senders and recievers, containing the
// startup and shutdown logic.
type AmqpBase struct {
	connection *amqp.Connection
	channel    *amqp.Channel
}

// Close the connection to amqp gracefully. Subclasses should ensure they finish
// all in-flight processing.
func (a AmqpBase) Close() {
	a.channel.Close()
	a.connection.Close()
}

// Connect to AMQP, and open a communication channel.
func (a *AmqpBase) dial(amqpURL string) (err error) {
	a.connection, err = amqp.Dial(amqpURL)
	if err != nil {
		log.Println("Could not connect to AMQP: ", err)
		return
	}

	a.channel, err = a.connection.Channel()
	if err != nil {
		log.Println("Could not open AMQP channel: ", err)
		return
	}

	return
}

// AmqpReceiver receives delayd commands over amqp
type AmqpReceiver struct {
	AmqpBase
	C    <-chan EntryWrapper
	rawC <-chan amqp.Delivery
}

// NewAmqpReceiver creates a new AmqpReceiver based on the provided AmqpConfig,
// and starts it listening for commands.
func NewAmqpReceiver(ac AmqpConfig) (receiver *AmqpReceiver, err error) {
	receiver = new(AmqpReceiver)

	err = receiver.dial(ac.URL)
	if err != nil {
		return
	}

	err = receiver.channel.ExchangeDeclare(ac.Exchange.Name, ac.Exchange.Kind, ac.Exchange.Durable, ac.Exchange.AutoDelete, ac.Exchange.Internal, ac.Exchange.NoWait, nil)
	if err != nil {
		log.Println("Could not declare AMQP Exchange: ", err)
		return
	}

	queue, err := receiver.channel.QueueDeclare(ac.Queue.Name, ac.Queue.Durable, ac.Queue.AutoDelete, ac.Queue.Exclusive, ac.Queue.NoWait, nil)
	if err != nil {
		log.Println("Could not declare AMQP Queue: ", err)
		return
	}

	for _, exch := range ac.Queue.Bind {
		log.Printf("Binding queue %s to exchange %s", queue.Name, exch)
		err = receiver.channel.QueueBind(queue.Name, "delayd", exch, ac.Queue.NoWait, nil)
		if err != nil {
			log.Fatalf("Error binding queue %s to Exchange %s", queue.Name, exch)
		}
	}

	//XXX set Qos here to match incoming concurrency
	messages, err := receiver.channel.Consume(queue.Name, "delayd", ac.Queue.AutoAck, ac.Queue.Exclusive, ac.Queue.NoLocal, ac.Queue.NoWait, nil)
	if err != nil {
		log.Println("Could not set up queue consume: ", err)
		return
	}

	c := make(chan EntryWrapper)
	receiver.C = c

	go func() {
		for {
			msg, ok := <-messages
			entry := Entry{}

			eWrapper := EntryWrapper{Msg: msg}

			// channel was closed. exit
			if !ok {
				return
			}

			delay, ok := msg.Headers["delayd-delay"].(int64)
			if !ok {
				log.Println("Bad/missing delay. discarding message")
				eWrapper.Done(false)
				continue
			}
			entry.SendAt = time.Now().Add(time.Duration(delay) * time.Millisecond)

			entry.Target, ok = msg.Headers["delayd-target"].(string)
			if !ok {
				log.Println("Bad/missing target. discarding message")
				eWrapper.Done(false)
				continue
			}

			// optional key value for overwrite
			h, ok := msg.Headers["delayd-key"].(string)
			if ok {
				entry.Key = h
			}

			// optional headers that will be relayed
			h, ok = msg.Headers["content-type"].(string)
			if ok {
				entry.ContentType = h
			}

			h, ok = msg.Headers["content-encoding"].(string)
			if ok {
				entry.ContentEncoding = h
			}

			h, ok = msg.Headers["correlation-id"].(string)
			if ok {
				entry.CorrelationID = h
			}

			entry.Body = msg.Body
			eWrapper.Entry = entry

			c <- eWrapper
		}
	}()

	return
}

// AmqpSender sends delayd entries over amqp after their timeout
type AmqpSender struct {
	AmqpBase

	C chan<- EntryWrapper
}

// NewAmqpSender creates a new AmqpSender connected to the given AMQP URL.
func NewAmqpSender(amqpURL string) (sender *AmqpSender, err error) {
	sender = new(AmqpSender)

	err = sender.dial(amqpURL)
	if err != nil {
		return
	}

	return
}

// Send sends a delayd entry over AMQP, using the entry's Target as the publish
// exchange.
func (s AmqpSender) Send(e Entry) (err error) {
	msg := amqp.Publishing{
		DeliveryMode:    amqp.Persistent,
		Timestamp:       time.Now(),
		ContentType:     e.ContentType,
		ContentEncoding: e.ContentEncoding,
		CorrelationId:   e.CorrelationID,
		Body:            e.Body,
	}

	err = s.channel.Publish(e.Target, "", true, false, msg)
	return
}
<|endoftext|>


<commit_before>// Copyright 2014 Arash Payan. All rights reserved.
// Use of this source code is governed by the Apache 2
// license that can be found in the LICENSE file.

// Package apns provides a client for using the Apple Push Notification service.
package apns

import (
	"bytes"
	"crypto/tls"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"log"
	"math/rand"
	"net"
	"time"
)

// ProductionGateway is the host address and port to pass NewClient for
// connecting to the production APNs.
const ProductionGateway = "gateway.push.apple.com:2195"

// SandboxGateway is the host address and port to pass NewClient for
// connecting to the development environment for the APNs.
const SandboxGateway = "gateway.sandbox.push.apple.com:2195"

// MaxPushNotificationBytes is the maximum number of bytes permitted in a payload
// sent to the APNs
const MaxPushNotificationBytes = 2048

// Constants related to the payload fields and their lengths.
const (
	deviceTokenItemID            = 1
	payloadItemID                = 2
	notificationIdentifierItemID = 3
	expirationDateItemID         = 4
	priorityItemID               = 5
	deviceTokenLength            = 32
	notificationIdentifierLength = 4
	expirationDateLength         = 4
	priorityLength               = 1
)

// initialized in init()
var apnsErrors map[int]string

// Push commands always start with command value 2.
const pushCommandValue = 2

// ErrClientNotConnected indicates the user need to call Connect() on the
// Client, due to never having connected or due to the connection erroring out.
var ErrClientNotConnected = errors.New("Client not connected to APNs")

// recentNotifications stores the last 25-50 notifications. When it gets full,
// it gets resliced, so only the latest 25 are retained.
var recentNotifications = make([]*Notification, 0, 50)

// Notification represents a push notification for a specific iOS device
type Notification struct {
	Alert       string      `json:"alert"`
	Badge       int16       `json:"badge"`
	Sound       string      `json:"sound"`
	AppData     interface{} `json:"-"`
	deviceToken string      `json:"-"`
	identifier  int32       `json:"-"`
	expiry      uint32      `json:"-"`
	priority    uint8       `json:"-"`
}

// APNs error values sent from Apple when a notification has an error.
const (
	processingErrorID    = 1
	missingDeviceTokenID = 2
	missingTopicID       = 3
	missingPayloadID     = 4
	invalidTokenSizeID   = 5
	invalidTopicSizeID   = 6
	invalidPayloadSizeID = 7
	invalidTokenID       = 8
	shutdownID           = 9
	noErrorID            = 10
)

func init() {
	apnsErrors = make(map[int]string)
	apnsErrors[0] = "No errors encountered"
	apnsErrors[processingErrorID] = "Processing error"
	apnsErrors[missingDeviceTokenID] = "Missing device token"
	apnsErrors[missingTopicID] = "Missing topic"
	apnsErrors[missingPayloadID] = "Missing payload"
	apnsErrors[invalidTokenSizeID] = "Invalid token size"
	apnsErrors[invalidTopicSizeID] = "Invalid topic size"
	apnsErrors[invalidPayloadSizeID] = "Invalid payload size"
	apnsErrors[invalidTokenID] = "Invalid token"
	apnsErrors[shutdownID] = "Shutdown"
	apnsErrors[noErrorID] = "None (unknown)"
}

// NewNotification creates an APNs notification that can be delivered to an iOS
// device with the specified devToken.
func NewNotification(devToken string) *Notification {
	n := &Notification{deviceToken: devToken}
	n.identifier = rand.New(rand.NewSource(time.Now().UnixNano())).Int31()
	return n
}

// Client is the broker between an APNs provider and the gateway
type Client struct {
	Gateway             string
	CertificateFile     string
	KeyFile             string
	conn                *tls.Conn
	IsConnected         bool
	notificationChan    chan *Notification
	invalidTokenHandler func(string)
}

// NewClient initializes a Client struct that you can use to send Notifications
// to the APNs. gateway should be either ProductionGateway or SandboxGateway.
// certFile and keyFile are paths to your Apple signed certificate and private
// key. invalidTokenHandler is a function that will be called when an attempt
// send a notification results in an invalid token error from the APNs. When
// you receive these errors, you should remove/disassociate the device token from
// your database/user. This function callback is provided as a simpler alternative
// to implementing a feedback service that would periodically poll Apple for
// invalid tokens.
func NewClient(gateway, certFile, keyFile string, invalidTokenHandler func(string)) *Client {
	c := Client{Gateway: gateway, CertificateFile: certFile, KeyFile: keyFile}
	c.notificationChan = make(chan *Notification, 4096) // 4096 oughtta be enough for anybody
	c.invalidTokenHandler = invalidTokenHandler
	return &c
}

func (c *Client) connect() error {
	c.IsConnected = false
	cert, err := tls.LoadX509KeyPair(c.CertificateFile, c.KeyFile)
	if err != nil {
		return err
	}

	host, _, err := net.SplitHostPort(c.Gateway)
	conf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		ServerName:   host,
	}

	c.conn, err = tls.Dial("tcp", c.Gateway, conf)
	if err != nil {
		return err
	}
	c.IsConnected = true
	return nil
}

// Connect establishes a connection to the APNs gateway specified in NewClient().
// This method blocks until the connection is established.
func (c *Client) Connect() error {
	err := c.connect()
	if err != nil {
		return err
	}

	// start a goroutine to read this connection for any errors messages
	go c.readErrors()

	// start a goroutine to process the queue of notifications
	go c.processQueue()

	return nil
}

func (c *Client) reconnect() {
	log.Print("Attempting to reconnect to APNs")
	maxSleepTime := 60 * time.Second
	// attempt to reconnect. sleeping (2 * attempt) seconds between each retry, with a max of 60 seconds
	for attempt := 0; ; attempt++ {
		sleepTime := time.Duration(attempt) * 2 * time.Second
		if sleepTime > maxSleepTime {
			time.Sleep(maxSleepTime)
		} else {
			time.Sleep(sleepTime)
		}
		err := c.connect()
		if err == nil {
			break
		}
	}

	go c.readErrors()
	go c.processQueue()
}

// Send queues a notification for sending to the APNs
func (c *Client) Send(n *Notification) {
	// if the channel is full, discard older messages, then queue the new one
	// if we don't discard the older messages, goroutine will get locked trying
	// to put a new notification on
	if len(c.notificationChan) == cap(c.notificationChan) {
		<-c.notificationChan
	}
	c.notificationChan <- n

	// we want to keep a cache of the last 25 or 50 messages, in case we get an error
	// from Apple
	if len(recentNotifications) >= 50 {
		recentNotifications = recentNotifications[25:]
	}
	recentNotifications = append(recentNotifications, n)
}

// readErrors sits on the socket waiting for any errors from the APNs. If it
// receives any data or an error, it will close the socket and report any
// relevant errors.
func (c *Client) readErrors() {
	readBuf := make([]byte, 6, 6)
	_, err := c.conn.Read(readBuf)
	// the connection should be closed anytime data is received or an error occurs
	// see apple docs for details
	c.IsConnected = false
	defer c.conn.Close()
	if err != nil {
		log.Printf("Error while reading APNs socket - %v", err)
		return
	}
	var id int32
	binary.Read(bytes.NewReader(readBuf[2:]), binary.BigEndian, &id)

	// if this is an invalid device token error, notify the user
	errID := int(readBuf[1])
	if errID == invalidTokenID {
		rn := recentNotifications // store a different pointer in case the slice gets changed
		for i := len(rn) - 1; i >= 0; i-- {
			n := rn[i]
			if n.identifier == id {
				c.invalidTokenHandler(n.deviceToken)
			}
		}
	} else {
		log.Printf("Error received for notification %d - %v", id, apnsErrors[errID])
	}
}

// processQueue pulls reads Notification objects out of a channel and tries to
// send them to Apple for delivery to a device.
func (c *Client) processQueue() {
	for n := range c.notificationChan {
		if !c.IsConnected {
			// put the notification back in the queue and reconnect this client
			c.notificationChan <- n
			go c.reconnect()
			return // we'll get restarted after the client reconnects
		}

		token, err := hex.DecodeString(n.deviceToken)
		if err != nil {
			log.Printf("Error decoding APNs notification token - %v", err)
			continue
		}

		payload := make(map[string]interface{})
		payload["aps"] = n
		if n.AppData != nil {
			payload["app_data"] = n.AppData
		}
		jsonBytes, err := json.Marshal(payload)
		if err != nil {
			log.Printf("Error marshaling payload to JSON - %v", err)
			continue
		}

		if len(jsonBytes) > MaxPushNotificationBytes {
			log.Printf("Notification is larger than the byte limit (%d). Skipping.", MaxPushNotificationBytes)
			continue
		}

		buf := &bytes.Buffer{}
		binary.Write(buf, binary.BigEndian, uint8(deviceTokenItemID))
		binary.Write(buf, binary.BigEndian, uint16(deviceTokenLength))
		binary.Write(buf, binary.BigEndian, token)
		binary.Write(buf, binary.BigEndian, uint8(payloadItemID))
		binary.Write(buf, binary.BigEndian, uint16(len(jsonBytes)))
		binary.Write(buf, binary.BigEndian, jsonBytes)
		binary.Write(buf, binary.BigEndian, uint8(notificationIdentifierItemID))
		binary.Write(buf, binary.BigEndian, uint16(notificationIdentifierLength))
		binary.Write(buf, binary.BigEndian, n.identifier)
		binary.Write(buf, binary.BigEndian, uint8(expirationDateItemID))
		binary.Write(buf, binary.BigEndian, uint16(expirationDateLength))
		binary.Write(buf, binary.BigEndian, n.expiry)
		binary.Write(buf, binary.BigEndian, uint8(priorityItemID))
		binary.Write(buf, binary.BigEndian, uint16(priorityLength))
		binary.Write(buf, binary.BigEndian, n.priority)

		fullBuf := &bytes.Buffer{}
		binary.Write(fullBuf, binary.BigEndian, uint8(pushCommandValue))
		binary.Write(fullBuf, binary.BigEndian, uint32(buf.Len()))
		binary.Write(fullBuf, binary.BigEndian, buf.Bytes())

		written, err := c.conn.Write(fullBuf.Bytes())
		if err != nil {
			log.Printf("Error writing notification %v to APNs - %v", n, err)
			c.conn.Close()
			c.IsConnected = false
			// requeue the notification and try again later
			c.notificationChan <- n
			go c.reconnect()
			return // we'll get restarted after the client reconnects
		}
		if written != fullBuf.Len() {
			log.Printf("Bytes written didn't equal the bytes in the buffer - w: %d, size: %d",
				written,
				fullBuf.Len())
			c.conn.Close()
			c.IsConnected = false
			c.notificationChan <- n
			go c.reconnect()
			return
		}
	}
}
<commit_msg>Remove unused error<commit_after>// Copyright 2014 Arash Payan. All rights reserved.
// Use of this source code is governed by the Apache 2
// license that can be found in the LICENSE file.

// Package apns provides a client for using the Apple Push Notification service.
package apns

import (
	"bytes"
	"crypto/tls"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"log"
	"math/rand"
	"net"
	"time"
)

// ProductionGateway is the host address and port to pass NewClient for
// connecting to the production APNs.
const ProductionGateway = "gateway.push.apple.com:2195"

// SandboxGateway is the host address and port to pass NewClient for
// connecting to the development environment for the APNs.
const SandboxGateway = "gateway.sandbox.push.apple.com:2195"

// MaxPushNotificationBytes is the maximum number of bytes permitted in a payload
// sent to the APNs
const MaxPushNotificationBytes = 2048

// Constants related to the payload fields and their lengths.
const (
	deviceTokenItemID            = 1
	payloadItemID                = 2
	notificationIdentifierItemID = 3
	expirationDateItemID         = 4
	priorityItemID               = 5
	deviceTokenLength            = 32
	notificationIdentifierLength = 4
	expirationDateLength         = 4
	priorityLength               = 1
)

// initialized in init()
var apnsErrors map[int]string

// Push commands always start with command value 2.
const pushCommandValue = 2

// recentNotifications stores the last 25-50 notifications. When it gets full,
// it gets resliced, so only the latest 25 are retained.
var recentNotifications = make([]*Notification, 0, 50)

// Notification represents a push notification for a specific iOS device
type Notification struct {
	Alert       string      `json:"alert"`
	Badge       int16       `json:"badge"`
	Sound       string      `json:"sound"`
	AppData     interface{} `json:"-"`
	deviceToken string      `json:"-"`
	identifier  int32       `json:"-"`
	expiry      uint32      `json:"-"`
	priority    uint8       `json:"-"`
}

// APNs error values sent from Apple when a notification has an error.
const (
	processingErrorID    = 1
	missingDeviceTokenID = 2
	missingTopicID       = 3
	missingPayloadID     = 4
	invalidTokenSizeID   = 5
	invalidTopicSizeID   = 6
	invalidPayloadSizeID = 7
	invalidTokenID       = 8
	shutdownID           = 9
	noErrorID            = 10
)

func init() {
	apnsErrors = make(map[int]string)
	apnsErrors[0] = "No errors encountered"
	apnsErrors[processingErrorID] = "Processing error"
	apnsErrors[missingDeviceTokenID] = "Missing device token"
	apnsErrors[missingTopicID] = "Missing topic"
	apnsErrors[missingPayloadID] = "Missing payload"
	apnsErrors[invalidTokenSizeID] = "Invalid token size"
	apnsErrors[invalidTopicSizeID] = "Invalid topic size"
	apnsErrors[invalidPayloadSizeID] = "Invalid payload size"
	apnsErrors[invalidTokenID] = "Invalid token"
	apnsErrors[shutdownID] = "Shutdown"
	apnsErrors[noErrorID] = "None (unknown)"
}

// NewNotification creates an APNs notification that can be delivered to an iOS
// device with the specified devToken.
func NewNotification(devToken string) *Notification {
	n := &Notification{deviceToken: devToken}
	n.identifier = rand.New(rand.NewSource(time.Now().UnixNano())).Int31()
	return n
}

// Client is the broker between an APNs provider and the gateway
type Client struct {
	Gateway             string
	CertificateFile     string
	KeyFile             string
	conn                *tls.Conn
	IsConnected         bool
	notificationChan    chan *Notification
	invalidTokenHandler func(string)
}

// NewClient initializes a Client struct that you can use to send Notifications
// to the APNs. gateway should be either ProductionGateway or SandboxGateway.
// certFile and keyFile are paths to your Apple signed certificate and private
// key. invalidTokenHandler is a function that will be called when an attempt
// send a notification results in an invalid token error from the APNs. When
// you receive these errors, you should remove/disassociate the device token from
// your database/user. This function callback is provided as a simpler alternative
// to implementing a feedback service that would periodically poll Apple for
// invalid tokens.
func NewClient(gateway, certFile, keyFile string, invalidTokenHandler func(string)) *Client {
	c := Client{Gateway: gateway, CertificateFile: certFile, KeyFile: keyFile}
	c.notificationChan = make(chan *Notification, 4096) // 4096 oughtta be enough for anybody
	c.invalidTokenHandler = invalidTokenHandler
	return &c
}

func (c *Client) connect() error {
	c.IsConnected = false
	cert, err := tls.LoadX509KeyPair(c.CertificateFile, c.KeyFile)
	if err != nil {
		return err
	}

	host, _, err := net.SplitHostPort(c.Gateway)
	conf := &tls.Config{
		Certificates: []tls.Certificate{cert},
		ServerName:   host,
	}

	c.conn, err = tls.Dial("tcp", c.Gateway, conf)
	if err != nil {
		return err
	}
	c.IsConnected = true
	return nil
}

// Connect establishes a connection to the APNs gateway specified in NewClient().
// This method blocks until the connection is established.
func (c *Client) Connect() error {
	err := c.connect()
	if err != nil {
		return err
	}

	// start a goroutine to read this connection for any errors messages
	go c.readErrors()

	// start a goroutine to process the queue of notifications
	go c.processQueue()

	return nil
}

func (c *Client) reconnect() {
	log.Print("Attempting to reconnect to APNs")
	maxSleepTime := 60 * time.Second
	// attempt to reconnect. sleeping (2 * attempt) seconds between each retry, with a max of 60 seconds
	for attempt := 0; ; attempt++ {
		sleepTime := time.Duration(attempt) * 2 * time.Second
		if sleepTime > maxSleepTime {
			time.Sleep(maxSleepTime)
		} else {
			time.Sleep(sleepTime)
		}
		err := c.connect()
		if err == nil {
			break
		}
	}

	go c.readErrors()
	go c.processQueue()
}

// Send queues a notification for sending to the APNs
func (c *Client) Send(n *Notification) {
	// if the channel is full, discard older messages, then queue the new one
	// if we don't discard the older messages, goroutine will get locked trying
	// to put a new notification on
	if len(c.notificationChan) == cap(c.notificationChan) {
		<-c.notificationChan
	}
	c.notificationChan <- n

	// we want to keep a cache of the last 25 or 50 messages, in case we get an error
	// from Apple
	if len(recentNotifications) >= 50 {
		recentNotifications = recentNotifications[25:]
	}
	recentNotifications = append(recentNotifications, n)
}

// readErrors sits on the socket waiting for any errors from the APNs. If it
// receives any data or an error, it will close the socket and report any
// relevant errors.
func (c *Client) readErrors() {
	readBuf := make([]byte, 6, 6)
	_, err := c.conn.Read(readBuf)
	// the connection should be closed anytime data is received or an error occurs
	// see apple docs for details
	c.IsConnected = false
	defer c.conn.Close()
	if err != nil {
		log.Printf("Error while reading APNs socket - %v", err)
		return
	}
	var id int32
	binary.Read(bytes.NewReader(readBuf[2:]), binary.BigEndian, &id)

	// if this is an invalid device token error, notify the user
	errID := int(readBuf[1])
	if errID == invalidTokenID {
		rn := recentNotifications // store a different pointer in case the slice gets changed
		for i := len(rn) - 1; i >= 0; i-- {
			n := rn[i]
			if n.identifier == id {
				c.invalidTokenHandler(n.deviceToken)
			}
		}
	} else {
		log.Printf("Error received for notification %d - %v", id, apnsErrors[errID])
	}
}

// processQueue pulls reads Notification objects out of a channel and tries to
// send them to Apple for delivery to a device.
func (c *Client) processQueue() {
	for n := range c.notificationChan {
		if !c.IsConnected {
			// put the notification back in the queue and reconnect this client
			c.notificationChan <- n
			go c.reconnect()
			return // we'll get restarted after the client reconnects
		}

		token, err := hex.DecodeString(n.deviceToken)
		if err != nil {
			log.Printf("Error decoding APNs notification token - %v", err)
			continue
		}

		payload := make(map[string]interface{})
		payload["aps"] = n
		if n.AppData != nil {
			payload["app_data"] = n.AppData
		}
		jsonBytes, err := json.Marshal(payload)
		if err != nil {
			log.Printf("Error marshaling payload to JSON - %v", err)
			continue
		}

		if len(jsonBytes) > MaxPushNotificationBytes {
			log.Printf("Notification is larger than the byte limit (%d). Skipping.", MaxPushNotificationBytes)
			continue
		}

		buf := &bytes.Buffer{}
		binary.Write(buf, binary.BigEndian, uint8(deviceTokenItemID))
		binary.Write(buf, binary.BigEndian, uint16(deviceTokenLength))
		binary.Write(buf, binary.BigEndian, token)
		binary.Write(buf, binary.BigEndian, uint8(payloadItemID))
		binary.Write(buf, binary.BigEndian, uint16(len(jsonBytes)))
		binary.Write(buf, binary.BigEndian, jsonBytes)
		binary.Write(buf, binary.BigEndian, uint8(notificationIdentifierItemID))
		binary.Write(buf, binary.BigEndian, uint16(notificationIdentifierLength))
		binary.Write(buf, binary.BigEndian, n.identifier)
		binary.Write(buf, binary.BigEndian, uint8(expirationDateItemID))
		binary.Write(buf, binary.BigEndian, uint16(expirationDateLength))
		binary.Write(buf, binary.BigEndian, n.expiry)
		binary.Write(buf, binary.BigEndian, uint8(priorityItemID))
		binary.Write(buf, binary.BigEndian, uint16(priorityLength))
		binary.Write(buf, binary.BigEndian, n.priority)

		fullBuf := &bytes.Buffer{}
		binary.Write(fullBuf, binary.BigEndian, uint8(pushCommandValue))
		binary.Write(fullBuf, binary.BigEndian, uint32(buf.Len()))
		binary.Write(fullBuf, binary.BigEndian, buf.Bytes())

		written, err := c.conn.Write(fullBuf.Bytes())
		if err != nil {
			log.Printf("Error writing notification %v to APNs - %v", n, err)
			c.conn.Close()
			c.IsConnected = false
			// requeue the notification and try again later
			c.notificationChan <- n
			go c.reconnect()
			return // we'll get restarted after the client reconnects
		}
		if written != fullBuf.Len() {
			log.Printf("Bytes written didn't equal the bytes in the buffer - w: %d, size: %d",
				written,
				fullBuf.Len())
			c.conn.Close()
			c.IsConnected = false
			c.notificationChan <- n
			go c.reconnect()
			return
		}
	}
}
<|endoftext|>