fix tests bump deps

release-0.4
Xiang Li 2013-12-01 17:24:30 -05:00
parent 67b4c27d5d
commit fc562bd625
33 changed files with 722 additions and 431 deletions

View File

@ -24,6 +24,6 @@ func TestV2DeleteKey(t *testing.T) {
resp, err = tests.DeleteForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{}) resp, err = tests.DeleteForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{})
body := tests.ReadBody(resp) body := tests.ReadBody(resp)
assert.Nil(t, err, "") assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","modifiedIndex":2,"createdIndex":1}}`, "") assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","prevValue":"XXX","modifiedIndex":2,"createdIndex":1}}`, "")
}) })
} }

View File

@ -65,7 +65,7 @@ func TestMultiNodeKillAllAndRecovery(t *testing.T) {
t.Fatalf("Recovery error: %s", err) t.Fatalf("Recovery error: %s", err)
} }
if result.ModifiedIndex != 16 { if result.Node.ModifiedIndex != 16 {
t.Fatalf("recovery failed! [%d/16]", result.ModifiedIndex) t.Fatalf("recovery failed! [%d/16]", result.Node.ModifiedIndex)
} }
} }

View File

@ -35,13 +35,13 @@ func TestRemoveNode(t *testing.T) {
fmt.Println("send remove to node3 and wait for its exiting") fmt.Println("send remove to node3 and wait for its exiting")
etcds[2].Wait() etcds[2].Wait()
resp, err := c.Get("_etcd/machines", false) resp, err := c.Get("_etcd/machines", false, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if len(resp.Kvs) != 2 { if len(resp.Node.Nodes) != 2 {
t.Fatal("cannot remove peer") t.Fatal("cannot remove peer")
} }
@ -59,14 +59,14 @@ func TestRemoveNode(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
resp, err = c.Get("_etcd/machines", false) resp, err = c.Get("_etcd/machines", false, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if len(resp.Kvs) != 3 { if len(resp.Node.Nodes) != 3 {
t.Fatalf("add peer fails #1 (%d != 3)", len(resp.Kvs)) t.Fatalf("add peer fails #1 (%d != 3)", len(resp.Node.Nodes))
} }
} }
@ -78,13 +78,13 @@ func TestRemoveNode(t *testing.T) {
client.Do(rmReq) client.Do(rmReq)
resp, err := c.Get("_etcd/machines", false) resp, err := c.Get("_etcd/machines", false, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if len(resp.Kvs) != 2 { if len(resp.Node.Nodes) != 2 {
t.Fatal("cannot remove peer") t.Fatal("cannot remove peer")
} }
@ -102,14 +102,14 @@ func TestRemoveNode(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
resp, err = c.Get("_etcd/machines", false) resp, err = c.Get("_etcd/machines", false, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if len(resp.Kvs) != 3 { if len(resp.Node.Nodes) != 3 {
t.Fatalf("add peer fails #2 (%d != 3)", len(resp.Kvs)) t.Fatalf("add peer fails #2 (%d != 3)", len(resp.Node.Nodes))
} }
} }
} }

View File

@ -39,24 +39,26 @@ func templateTestSimpleMultiNode(t *testing.T, tls bool) {
// Test Set // Test Set
result, err := c.Set("foo", "bar", 100) result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set 1 failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
time.Sleep(time.Second) time.Sleep(time.Second)
result, err = c.Set("foo", "bar", 100) result, err = c.Set("foo", "bar", 100)
node = result.Node
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.PrevValue != "bar" || result.TTL < 95 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.PrevValue != "bar" || node.TTL < 95 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatalf("Set 2 failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set 2 failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
} }

View File

@ -30,13 +30,14 @@ func TestSimpleSnapshot(t *testing.T) {
// issue first 501 commands // issue first 501 commands
for i := 0; i < 501; i++ { for i := 0; i < 501; i++ {
result, err := c.Set("foo", "bar", 100) result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatalf("Set failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
} }
@ -62,13 +63,14 @@ func TestSimpleSnapshot(t *testing.T) {
// issue second 501 commands // issue second 501 commands
for i := 0; i < 501; i++ { for i := 0; i < 501; i++ {
result, err := c.Set("foo", "bar", 100) result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatalf("Set failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
} }

View File

@ -28,13 +28,14 @@ func TestSingleNodeRecovery(t *testing.T) {
c.SyncCluster() c.SyncCluster()
// Test Set // Test Set
result, err := c.Set("foo", "bar", 100) result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set 1 failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
time.Sleep(time.Second) time.Sleep(time.Second)
@ -50,16 +51,18 @@ func TestSingleNodeRecovery(t *testing.T) {
time.Sleep(time.Second) time.Sleep(time.Second)
result, err = c.Get("foo", false) result, err = c.Get("foo", false, false)
node = result.Node
if err != nil { if err != nil {
t.Fatal("get fail: " + err.Error()) t.Fatal("get fail: " + err.Error())
return return
} }
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL > 99 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL > 99 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatalf("Recovery Get failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Recovery Get failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
} }

View File

@ -28,36 +28,39 @@ func TestSingleNode(t *testing.T) {
c.SyncCluster() c.SyncCluster()
// Test Set // Test Set
result, err := c.Set("foo", "bar", 100) result, err := c.Set("foo", "bar", 100)
node := result.Node
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.TTL < 95 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.TTL < 95 {
if err != nil { if err != nil {
t.Fatal("Set 1: ", err) t.Fatal("Set 1: ", err)
} }
t.Fatalf("Set 1 failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set 1 failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
time.Sleep(time.Second) time.Sleep(time.Second)
result, err = c.Set("foo", "bar", 100) result, err = c.Set("foo", "bar", 100)
node = result.Node
if err != nil || result.Key != "/foo" || result.Value != "bar" || result.PrevValue != "bar" || result.TTL != 100 { if err != nil || node.Key != "/foo" || node.Value != "bar" || node.PrevValue != "bar" || node.TTL != 100 {
if err != nil { if err != nil {
t.Fatal("Set 2: ", err) t.Fatal("Set 2: ", err)
} }
t.Fatalf("Set 2 failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set 2 failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
// Add a test-and-set test // Add a test-and-set test
// First, we'll test we can change the value if we get it write // First, we'll test we can change the value if we get it write
result, err = c.CompareAndSwap("foo", "foobar", 100, "bar", 0) result, err = c.CompareAndSwap("foo", "foobar", 100, "bar", 0)
node = result.Node
if err != nil || result.Key != "/foo" || result.Value != "foobar" || result.PrevValue != "bar" || result.TTL != 100 { if err != nil || node.Key != "/foo" || node.Value != "foobar" || node.PrevValue != "bar" || node.TTL != 100 {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Fatalf("Set 3 failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Set 3 failed with %s %s %v", node.Key, node.Value, node.TTL)
} }
// Next, we'll make sure we can't set it without the correct prior value // Next, we'll make sure we can't set it without the correct prior value

View File

@ -44,7 +44,7 @@ func Set(stop chan bool) {
result, err := c.Set(key, "bar", 0) result, err := c.Set(key, "bar", 0)
if err != nil || result.Key != "/"+key || result.Value != "bar" { if err != nil || result.Node.Key != "/"+key || result.Node.Value != "bar" {
select { select {
case <-stop: case <-stop:
stopSet = true stopSet = true

View File

@ -99,5 +99,5 @@ func TestV1ClusterMigration(t *testing.T) {
body = tests.ReadBody(resp) body = tests.ReadBody(resp)
assert.Nil(t, err, "") assert.Nil(t, err, "")
assert.Equal(t, resp.StatusCode, 200, "") assert.Equal(t, resp.StatusCode, 200, "")
assert.Equal(t, string(body), `{"action":"get","key":"/foo","value":"one","modifiedIndex":9}`) assert.Equal(t, string(body), `{"action":"get","node":{"key":"/foo","value":"one","modifiedIndex":9,"createdIndex":9}}`)
} }

View File

@ -2,10 +2,22 @@ package etcd
// Add a new directory with a random etcd-generated key under the given path. // Add a new directory with a random etcd-generated key under the given path.
func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) { func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) {
return c.post(key, "", ttl) raw, err := c.post(key, "", ttl)
if err != nil {
return nil, err
}
return raw.toResponse()
} }
// Add a new file with a random etcd-generated key under the given path. // Add a new file with a random etcd-generated key under the given path.
func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) { func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) {
return c.post(key, value, ttl) raw, err := c.post(key, value, ttl)
if err != nil {
return nil, err
}
return raw.toResponse()
} }

View File

@ -5,8 +5,8 @@ import "testing"
func TestAddChild(t *testing.T) { func TestAddChild(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("fooDir") c.Delete("fooDir", true)
c.DeleteAll("nonexistentDir") c.Delete("nonexistentDir", true)
}() }()
c.SetDir("fooDir", 5) c.SetDir("fooDir", 5)
@ -21,10 +21,10 @@ func TestAddChild(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
resp, err := c.Get("fooDir", true) resp, err := c.Get("fooDir", true, false)
// The child with v0 should proceed the child with v1 because it's added // The child with v0 should proceed the child with v1 because it's added
// earlier, so it should have a lower key. // earlier, so it should have a lower key.
if !(len(resp.Kvs) == 2 && (resp.Kvs[0].Value == "v0" && resp.Kvs[1].Value == "v1")) { if !(len(resp.Node.Nodes) == 2 && (resp.Node.Nodes[0].Value == "v0" && resp.Node.Nodes[1].Value == "v1")) {
t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
" The response was: %#v", resp) " The response was: %#v", resp)
} }
@ -40,8 +40,8 @@ func TestAddChild(t *testing.T) {
func TestAddChildDir(t *testing.T) { func TestAddChildDir(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("fooDir") c.Delete("fooDir", true)
c.DeleteAll("nonexistentDir") c.Delete("nonexistentDir", true)
}() }()
c.SetDir("fooDir", 5) c.SetDir("fooDir", 5)
@ -56,10 +56,10 @@ func TestAddChildDir(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
resp, err := c.Get("fooDir", true) resp, err := c.Get("fooDir", true, false)
// The child with v0 should proceed the child with v1 because it's added // The child with v0 should proceed the child with v1 because it's added
// earlier, so it should have a lower key. // earlier, so it should have a lower key.
if !(len(resp.Kvs) == 2 && (len(resp.Kvs[0].KVPairs) == 0 && len(resp.Kvs[1].KVPairs) == 0)) { if !(len(resp.Node.Nodes) == 2 && (len(resp.Node.Nodes[0].Nodes) == 0 && len(resp.Node.Nodes[1].Nodes) == 0)) {
t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
" The response was: %#v", resp) " The response was: %#v", resp)
} }

View File

@ -11,7 +11,6 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"reflect"
"strings" "strings"
"time" "time"
) )
@ -30,6 +29,10 @@ const (
WEAK_CONSISTENCY = "WEAK" WEAK_CONSISTENCY = "WEAK"
) )
const (
defaultBufferSize = 10
)
type Cluster struct { type Cluster struct {
Leader string `json:"leader"` Leader string `json:"leader"`
Machines []string `json:"machines"` Machines []string `json:"machines"`
@ -48,14 +51,9 @@ type Client struct {
config Config `json:"config"` config Config `json:"config"`
httpClient *http.Client httpClient *http.Client
persistence io.Writer persistence io.Writer
cURLch chan string
} }
type options map[string]interface{}
// An internally-used data structure that represents a mapping
// between valid options and their kinds
type validOptions map[string]reflect.Kind
// NewClient create a basic client that is configured to be used // NewClient create a basic client that is configured to be used
// with the given machine list. // with the given machine list.
func NewClient(machines []string) *Client { func NewClient(machines []string) *Client {
@ -333,9 +331,7 @@ func dialTimeout(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, time.Second) return net.DialTimeout(network, addr, time.Second)
} }
func (c *Client) updateLeader(httpPath string) { func (c *Client) updateLeader(u *url.URL) {
u, _ := url.Parse(httpPath)
var leader string var leader string
if u.Scheme == "" { if u.Scheme == "" {
leader = "http://" + u.Host leader = "http://" + u.Host
@ -347,3 +343,32 @@ func (c *Client) updateLeader(httpPath string) {
c.cluster.Leader = leader c.cluster.Leader = leader
c.saveConfig() c.saveConfig()
} }
// switchLeader switch the current leader to machines[num]
func (c *Client) switchLeader(num int) {
logger.Debugf("switch.leader[from %v to %v]",
c.cluster.Leader, c.cluster.Machines[num])
c.cluster.Leader = c.cluster.Machines[num]
}
func (c *Client) OpenCURL() {
c.cURLch = make(chan string, defaultBufferSize)
}
func (c *Client) CloseCURL() {
c.cURLch = nil
}
func (c *Client) sendCURL(command string) {
go func() {
select {
case c.cURLch <- command:
default:
}
}()
}
func (c *Client) RecvCURL() string {
return <-c.cURLch
}

View File

@ -14,5 +14,12 @@ func (c *Client) CompareAndSwap(key string, value string, ttl uint64, prevValue
if prevIndex != 0 { if prevIndex != 0 {
options["prevIndex"] = prevIndex options["prevIndex"] = prevIndex
} }
return c.put(key, value, ttl, options)
raw, err := c.put(key, value, ttl, options)
if err != nil {
return nil, err
}
return raw.toResponse()
} }

View File

@ -7,7 +7,7 @@ import (
func TestCompareAndSwap(t *testing.T) { func TestCompareAndSwap(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("foo") c.Delete("foo", true)
}() }()
c.Set("foo", "bar", 5) c.Set("foo", "bar", 5)
@ -17,8 +17,8 @@ func TestCompareAndSwap(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Value == "bar2" && resp.PrevValue == "bar" && if !(resp.Node.Value == "bar2" && resp.Node.PrevValue == "bar" &&
resp.Key == "/foo" && resp.TTL == 5) { resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
t.Fatalf("CompareAndSwap 1 failed: %#v", resp) t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
} }
@ -34,12 +34,12 @@ func TestCompareAndSwap(t *testing.T) {
} }
// This should succeed // This should succeed
resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.ModifiedIndex) resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.Node.ModifiedIndex)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Value == "bar2" && resp.PrevValue == "bar" && if !(resp.Node.Value == "bar2" && resp.Node.PrevValue == "bar" &&
resp.Key == "/foo" && resp.TTL == 5) { resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
t.Fatalf("CompareAndSwap 1 failed: %#v", resp) t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
} }

View File

@ -1,16 +1,15 @@
package etcd package etcd
import ( import (
"github.com/coreos/go-log/log"
"os" "os"
"github.com/coreos/go-log/log"
) )
var logger *log.Logger var logger *log.Logger
func init() { func init() {
setLogger(log.PriErr) setLogger(log.PriErr)
// Uncomment the following line if you want to see lots of logs
// OpenDebug()
} }
func OpenDebug() { func OpenDebug() {

View File

@ -1,17 +1,26 @@
package etcd package etcd
// DeleteAll deletes everything under the given key. If the key // Delete deletes the given key.
// points to a file, the file will be deleted. If the key points // When recursive set to false If the key points to a
// to a directory, then everything under the directory, include // directory, the method will fail.
// When recursive set to true, if the key points to a file,
// the file will be deleted. If the key points
// to a directory, then everything under the directory, including
// all child directories, will be deleted. // all child directories, will be deleted.
func (c *Client) DeleteAll(key string) (*Response, error) { func (c *Client) Delete(key string, recursive bool) (*Response, error) {
return c.delete(key, options{ raw, err := c.DeleteRaw(key, recursive)
"recursive": true,
}) if err != nil {
return nil, err
}
return raw.toResponse()
} }
// Delete deletes the given key. If the key points to a func (c *Client) DeleteRaw(key string, recursive bool) (*RawResponse, error) {
// directory, the method will fail. ops := options{
func (c *Client) Delete(key string) (*Response, error) { "recursive": recursive,
return c.delete(key, nil) }
return c.delete(key, ops)
} }

View File

@ -7,21 +7,21 @@ import (
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("foo") c.Delete("foo", true)
}() }()
c.Set("foo", "bar", 5) c.Set("foo", "bar", 5)
resp, err := c.Delete("foo") resp, err := c.Delete("foo", false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.PrevValue == "bar" && resp.Value == "") { if !(resp.Node.PrevValue == "bar" && resp.Node.Value == "") {
t.Fatalf("Delete failed with %s %s", resp.PrevValue, t.Fatalf("Delete failed with %s %s", resp.Node.PrevValue,
resp.Value) resp.Node.Value)
} }
resp, err = c.Delete("foo") resp, err = c.Delete("foo", false)
if err == nil { if err == nil {
t.Fatalf("Delete should have failed because the key foo did not exist. "+ t.Fatalf("Delete should have failed because the key foo did not exist. "+
"The response was: %v", resp) "The response was: %v", resp)
@ -31,32 +31,32 @@ func TestDelete(t *testing.T) {
func TestDeleteAll(t *testing.T) { func TestDeleteAll(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("foo") c.Delete("foo", true)
c.DeleteAll("fooDir") c.Delete("fooDir", true)
}() }()
c.Set("foo", "bar", 5) c.Set("foo", "bar", 5)
resp, err := c.DeleteAll("foo") resp, err := c.Delete("foo", true)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.PrevValue == "bar" && resp.Value == "") { if !(resp.Node.PrevValue == "bar" && resp.Node.Value == "") {
t.Fatalf("DeleteAll 1 failed: %#v", resp) t.Fatalf("DeleteAll 1 failed: %#v", resp)
} }
c.SetDir("fooDir", 5) c.SetDir("fooDir", 5)
c.Set("fooDir/foo", "bar", 5) c.Set("fooDir/foo", "bar", 5)
resp, err = c.DeleteAll("fooDir") resp, err = c.Delete("fooDir", true)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.PrevValue == "" && resp.Value == "") { if !(resp.Node.PrevValue == "" && resp.Node.Value == "") {
t.Fatalf("DeleteAll 2 failed: %#v", resp) t.Fatalf("DeleteAll 2 failed: %#v", resp)
} }
resp, err = c.DeleteAll("foo") resp, err = c.Delete("foo", true)
if err == nil { if err == nil {
t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+ t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+
"The response was: %v", resp) "The response was: %v", resp)

View File

@ -1,23 +1,27 @@
package etcd package etcd
// GetDir gets the all contents under the given key.
// If the key points to a file, the file is returned.
// If the key points to a directory, everything under it is returnd,
// including all contents under all child directories.
func (c *Client) GetAll(key string, sort bool) (*Response, error) {
return c.get(key, options{
"recursive": true,
"sorted": sort,
})
}
// Get gets the file or directory associated with the given key. // Get gets the file or directory associated with the given key.
// If the key points to a directory, files and directories under // If the key points to a directory, files and directories under
// it will be returned in sorted or unsorted order, depending on // it will be returned in sorted or unsorted order, depending on
// the sort flag. Note that contents under child directories // the sort flag.
// will not be returned. To get those contents, use GetAll. // If recursive is set to false, contents under child directories
func (c *Client) Get(key string, sort bool) (*Response, error) { // will not be returned.
return c.get(key, options{ // If recursive is set to true, all the contents will be returned.
"sorted": sort, func (c *Client) Get(key string, sort, recursive bool) (*Response, error) {
}) raw, err := c.RawGet(key, sort, recursive)
if err != nil {
return nil, err
}
return raw.toResponse()
}
func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) {
ops := options{
"recursive": recursive,
"sorted": sort,
}
return c.get(key, ops)
} }

View File

@ -8,22 +8,22 @@ import (
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("foo") c.Delete("foo", true)
}() }()
c.Set("foo", "bar", 5) c.Set("foo", "bar", 5)
result, err := c.Get("foo", false) result, err := c.Get("foo", false, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if result.Key != "/foo" || result.Value != "bar" { if result.Node.Key != "/foo" || result.Node.Value != "bar" {
t.Fatalf("Get failed with %s %s %v", result.Key, result.Value, result.TTL) t.Fatalf("Get failed with %s %s %v", result.Node.Key, result.Node.Value, result.Node.TTL)
} }
result, err = c.Get("goo", false) result, err = c.Get("goo", false, false)
if err == nil { if err == nil {
t.Fatalf("should not be able to get non-exist key") t.Fatalf("should not be able to get non-exist key")
} }
@ -32,7 +32,7 @@ func TestGet(t *testing.T) {
func TestGetAll(t *testing.T) { func TestGetAll(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("fooDir") c.Delete("fooDir", true)
}() }()
c.SetDir("fooDir", 5) c.SetDir("fooDir", 5)
@ -40,25 +40,36 @@ func TestGetAll(t *testing.T) {
c.Set("fooDir/k1", "v1", 5) c.Set("fooDir/k1", "v1", 5)
// Return kv-pairs in sorted order // Return kv-pairs in sorted order
result, err := c.Get("fooDir", true) result, err := c.Get("fooDir", true, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected := kvPairs{ expected := Nodes{
KeyValuePair{ Node{
Key: "/fooDir/k0", Key: "/fooDir/k0",
Value: "v0", Value: "v0",
TTL: 5,
ModifiedIndex: 31,
CreatedIndex: 31,
}, },
KeyValuePair{ Node{
Key: "/fooDir/k1", Key: "/fooDir/k1",
Value: "v1", Value: "v1",
TTL: 5,
ModifiedIndex: 32,
CreatedIndex: 32,
}, },
} }
if !reflect.DeepEqual(result.Kvs, expected) { // do not check expiration time, too hard to fake
t.Fatalf("(actual) %v != (expected) %v", result.Kvs, expected) for i, _ := range result.Node.Nodes {
result.Node.Nodes[i].Expiration = nil
}
if !reflect.DeepEqual(result.Node.Nodes, expected) {
t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
} }
// Test the `recursive` option // Test the `recursive` option
@ -66,34 +77,44 @@ func TestGetAll(t *testing.T) {
c.Set("fooDir/childDir/k2", "v2", 5) c.Set("fooDir/childDir/k2", "v2", 5)
// Return kv-pairs in sorted order // Return kv-pairs in sorted order
result, err = c.GetAll("fooDir", true) result, err = c.Get("fooDir", true, true)
// do not check expiration time, too hard to fake
result.Node.Expiration = nil
for i, _ := range result.Node.Nodes {
result.Node.Nodes[i].Expiration = nil
}
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected = kvPairs{ expected = Nodes{
KeyValuePair{ Node{
Key: "/fooDir/childDir", Key: "/fooDir/childDir",
Dir: true, Dir: true,
KVPairs: kvPairs{ Nodes: Nodes{
KeyValuePair{ Node{
Key: "/fooDir/childDir/k2", Key: "/fooDir/childDir/k2",
Value: "v2", Value: "v2",
TTL: 5,
}, },
}, },
TTL: 5,
}, },
KeyValuePair{ Node{
Key: "/fooDir/k0", Key: "/fooDir/k0",
Value: "v0", Value: "v0",
TTL: 5,
}, },
KeyValuePair{ Node{
Key: "/fooDir/k1", Key: "/fooDir/k1",
Value: "v1", Value: "v1",
TTL: 5,
}, },
} }
if !reflect.DeepEqual(result.Kvs, expected) { if !reflect.DeepEqual(result.Node.Nodes, expected) {
t.Fatalf("(actual) %v != (expected) %v", result.Kvs) t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
} }
} }

View File

@ -0,0 +1,68 @@
package etcd
import (
"fmt"
"net/url"
"reflect"
)
type options map[string]interface{}
// An internally-used data structure that represents a mapping
// between valid options and their kinds
type validOptions map[string]reflect.Kind
// Valid options for GET, PUT, POST, DELETE
// Using CAPITALIZED_UNDERSCORE to emphasize that these
// values are meant to be used as constants.
var (
VALID_GET_OPTIONS = validOptions{
"recursive": reflect.Bool,
"consistent": reflect.Bool,
"sorted": reflect.Bool,
"wait": reflect.Bool,
"waitIndex": reflect.Uint64,
}
VALID_PUT_OPTIONS = validOptions{
"prevValue": reflect.String,
"prevIndex": reflect.Uint64,
"prevExist": reflect.Bool,
}
VALID_POST_OPTIONS = validOptions{}
VALID_DELETE_OPTIONS = validOptions{
"recursive": reflect.Bool,
}
)
// Convert options to a string of HTML parameters
func (ops options) toParameters(validOps validOptions) (string, error) {
p := "?"
values := url.Values{}
if ops == nil {
return "", nil
}
for k, v := range ops {
// Check if the given option is valid (that it exists)
kind := validOps[k]
if kind == reflect.Invalid {
return "", fmt.Errorf("Invalid option: %v", k)
}
// Check if the given option is of the valid type
t := reflect.TypeOf(v)
if kind != t.Kind() {
return "", fmt.Errorf("Option %s should be of %v kind, not of %v kind.",
k, kind, t.Kind())
}
values.Set(k, fmt.Sprintf("%v", v))
}
p += values.Encode()
return p, nil
}

View File

@ -1,54 +1,18 @@
package etcd package etcd
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"reflect"
"strings" "strings"
"time" "time"
) )
// Valid options for GET, PUT, POST, DELETE
// Using CAPITALIZED_UNDERSCORE to emphasize that these
// values are meant to be used as constants.
var (
VALID_GET_OPTIONS = validOptions{
"recursive": reflect.Bool,
"consistent": reflect.Bool,
"sorted": reflect.Bool,
"wait": reflect.Bool,
"waitIndex": reflect.Uint64,
}
VALID_PUT_OPTIONS = validOptions{
"prevValue": reflect.String,
"prevIndex": reflect.Uint64,
"prevExist": reflect.Bool,
}
VALID_POST_OPTIONS = validOptions{}
VALID_DELETE_OPTIONS = validOptions{
"recursive": reflect.Bool,
}
curlChan chan string
)
// SetCurlChan sets a channel to which cURL commands which can be used to
// re-produce requests are sent. This is useful for debugging.
func SetCurlChan(c chan string) {
curlChan = c
}
// get issues a GET request // get issues a GET request
func (c *Client) get(key string, options options) (*Response, error) { func (c *Client) get(key string, options options) (*RawResponse, error) {
logger.Debugf("get %s [%s]", key, c.cluster.Leader) logger.Debugf("get %s [%s]", key, c.cluster.Leader)
p := path.Join("keys", key) p := path.Join("keys", key)
@ -57,15 +21,14 @@ func (c *Client) get(key string, options options) (*Response, error) {
if c.config.Consistency == STRONG_CONSISTENCY { if c.config.Consistency == STRONG_CONSISTENCY {
options["consistent"] = true options["consistent"] = true
} }
if options != nil {
str, err := optionsToString(options, VALID_GET_OPTIONS)
if err != nil {
return nil, err
}
p += str
}
resp, err := c.sendRequest("GET", p, url.Values{}) str, err := options.toParameters(VALID_GET_OPTIONS)
if err != nil {
return nil, err
}
p += str
resp, err := c.sendRequest("GET", p, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -75,28 +38,19 @@ func (c *Client) get(key string, options options) (*Response, error) {
} }
// put issues a PUT request // put issues a PUT request
func (c *Client) put(key string, value string, ttl uint64, options options) (*Response, error) { func (c *Client) put(key string, value string, ttl uint64,
options options) (*RawResponse, error) {
logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
v := url.Values{}
if value != "" {
v.Set("value", value)
}
if ttl > 0 {
v.Set("ttl", fmt.Sprintf("%v", ttl))
}
p := path.Join("keys", key) p := path.Join("keys", key)
if options != nil {
str, err := optionsToString(options, VALID_PUT_OPTIONS)
if err != nil {
return nil, err
}
p += str
}
resp, err := c.sendRequest("PUT", p, v) str, err := options.toParameters(VALID_PUT_OPTIONS)
if err != nil {
return nil, err
}
p += str
resp, err := c.sendRequest("PUT", p, buildValues(value, ttl))
if err != nil { if err != nil {
return nil, err return nil, err
@ -106,19 +60,11 @@ func (c *Client) put(key string, value string, ttl uint64, options options) (*Re
} }
// post issues a POST request // post issues a POST request
func (c *Client) post(key string, value string, ttl uint64) (*Response, error) { func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) {
logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
v := url.Values{} p := path.Join("keys", key)
if value != "" { resp, err := c.sendRequest("POST", p, buildValues(value, ttl))
v.Set("value", value)
}
if ttl > 0 {
v.Set("ttl", fmt.Sprintf("%v", ttl))
}
resp, err := c.sendRequest("POST", path.Join("keys", key), v)
if err != nil { if err != nil {
return nil, err return nil, err
@ -128,20 +74,18 @@ func (c *Client) post(key string, value string, ttl uint64) (*Response, error) {
} }
// delete issues a DELETE request // delete issues a DELETE request
func (c *Client) delete(key string, options options) (*Response, error) { func (c *Client) delete(key string, options options) (*RawResponse, error) {
logger.Debugf("delete %s [%s]", key, c.cluster.Leader) logger.Debugf("delete %s [%s]", key, c.cluster.Leader)
v := url.Values{}
p := path.Join("keys", key) p := path.Join("keys", key)
if options != nil {
str, err := optionsToString(options, VALID_DELETE_OPTIONS)
if err != nil {
return nil, err
}
p += str
}
resp, err := c.sendRequest("DELETE", p, v) str, err := options.toParameters(VALID_DELETE_OPTIONS)
if err != nil {
return nil, err
}
p += str
resp, err := c.sendRequest("DELETE", p, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -151,126 +95,128 @@ func (c *Client) delete(key string, options options) (*Response, error) {
} }
// sendRequest sends a HTTP request and returns a Response as defined by etcd // sendRequest sends a HTTP request and returns a Response as defined by etcd
func (c *Client) sendRequest(method string, _path string, values url.Values) (*Response, error) { func (c *Client) sendRequest(method string, relativePath string,
var body string = values.Encode() values url.Values) (*RawResponse, error) {
var resp *http.Response
var req *http.Request var req *http.Request
var resp *http.Response
var httpPath string
var err error
var b []byte
trial := 0
retry := 0
// if we connect to a follower, we will retry until we found a leader // if we connect to a follower, we will retry until we found a leader
for { for {
var httpPath string trial++
logger.Debug("begin trail ", trial)
// If _path has schema already, then it's assumed to be if trial > 2*len(c.cluster.Machines) {
// a complete URL and therefore needs no further processing. return nil, fmt.Errorf("Cannot reach servers after %v time", trial)
u, err := url.Parse(_path)
if err != nil {
return nil, err
} }
if u.Scheme != "" { if method == "GET" && c.config.Consistency == WEAK_CONSISTENCY {
httpPath = _path // If it's a GET and consistency level is set to WEAK,
// then use a random machine.
httpPath = c.getHttpPath(true, relativePath)
} else { } else {
if method == "GET" && c.config.Consistency == WEAK_CONSISTENCY { // Else use the leader.
// If it's a GET and consistency level is set to WEAK, httpPath = c.getHttpPath(false, relativePath)
// then use a random machine.
httpPath = c.getHttpPath(true, _path)
} else {
// Else use the leader.
httpPath = c.getHttpPath(false, _path)
}
} }
// Return a cURL command if curlChan is set // Return a cURL command if curlChan is set
if curlChan != nil { if c.cURLch != nil {
command := fmt.Sprintf("curl -X %s %s", method, httpPath) command := fmt.Sprintf("curl -X %s %s", method, httpPath)
for key, value := range values { for key, value := range values {
command += fmt.Sprintf(" -d %s=%s", key, value[0]) command += fmt.Sprintf(" -d %s=%s", key, value[0])
} }
curlChan <- command c.sendCURL(command)
} }
logger.Debug("send.request.to ", httpPath, " | method ", method) logger.Debug("send.request.to ", httpPath, " | method ", method)
if body == "" {
if values == nil {
req, _ = http.NewRequest(method, httpPath, nil) req, _ = http.NewRequest(method, httpPath, nil)
} else { } else {
req, _ = http.NewRequest(method, httpPath, strings.NewReader(body)) req, _ = http.NewRequest(method, httpPath,
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") strings.NewReader(values.Encode()))
req.Header.Set("Content-Type",
"application/x-www-form-urlencoded; param=value")
} }
resp, err = c.httpClient.Do(req)
logger.Debug("recv.response.from ", httpPath)
// network error, change a machine! // network error, change a machine!
if err != nil { if resp, err = c.httpClient.Do(req); err != nil {
retry++ c.switchLeader(trial % len(c.cluster.Machines))
if retry > 2*len(c.cluster.Machines) {
return nil, errors.New("Cannot reach servers")
}
num := retry % len(c.cluster.Machines)
logger.Debug("update.leader[", c.cluster.Leader, ",", c.cluster.Machines[num], "]")
c.cluster.Leader = c.cluster.Machines[num]
time.Sleep(time.Millisecond * 200) time.Sleep(time.Millisecond * 200)
continue continue
} }
if resp != nil { if resp != nil {
if resp.StatusCode == http.StatusTemporaryRedirect { logger.Debug("recv.response.from ", httpPath)
httpPath := resp.Header.Get("Location")
resp.Body.Close() var ok bool
ok, b = c.handleResp(resp)
if httpPath == "" { if !ok {
return nil, errors.New("Cannot get redirection location")
}
c.updateLeader(httpPath)
logger.Debug("send.redirect")
// try to connect the leader
continue continue
} else if resp.StatusCode == http.StatusInternalServerError {
resp.Body.Close()
retry++
if retry > 2*len(c.cluster.Machines) {
return nil, errors.New("Cannot reach servers")
}
continue
} else {
logger.Debug("send.return.response ", httpPath)
break
} }
logger.Debug("recv.success.", httpPath)
break
} }
logger.Debug("error.from ", httpPath, " ", err.Error())
// should not reach here
// err and resp should not be nil at the same time
logger.Debug("error.from ", httpPath)
return nil, err return nil, err
} }
// Convert HTTP response to etcd response r := &RawResponse{
b, err := ioutil.ReadAll(resp.Body) StatusCode: resp.StatusCode,
Body: b,
resp.Body.Close() Header: resp.Header,
if err != nil {
return nil, err
} }
if !(resp.StatusCode == http.StatusOK || return r, nil
resp.StatusCode == http.StatusCreated) { }
return nil, handleError(b)
// handleResp handles the responses from the etcd server
// If status code is OK, read the http body and return it as byte array
// If status code is TemporaryRedirect, update leader.
// If status code is InternalServerError, sleep for 200ms.
func (c *Client) handleResp(resp *http.Response) (bool, []byte) {
defer resp.Body.Close()
code := resp.StatusCode
if code == http.StatusTemporaryRedirect {
u, err := resp.Location()
if err != nil {
logger.Warning(err)
} else {
c.updateLeader(u)
}
return false, nil
} else if code == http.StatusInternalServerError {
time.Sleep(time.Millisecond * 200)
} else if code == http.StatusOK ||
code == http.StatusCreated ||
code == http.StatusBadRequest {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, nil
}
return true, b
} }
var result Response logger.Warning("bad status code ", resp.StatusCode)
return false, nil
err = json.Unmarshal(b, &result)
if err != nil {
return nil, err
}
return &result, nil
} }
func (c *Client) getHttpPath(random bool, s ...string) string { func (c *Client) getHttpPath(random bool, s ...string) string {
@ -288,3 +234,18 @@ func (c *Client) getHttpPath(random bool, s ...string) string {
return fullPath return fullPath
} }
// buildValues builds a url.Values map according to the given value and ttl
func buildValues(value string, ttl uint64) url.Values {
v := url.Values{}
if value != "" {
v.Set("value", value)
}
if ttl > 0 {
v.Set("ttl", fmt.Sprintf("%v", ttl))
}
return v
}

View File

@ -1,51 +1,68 @@
package etcd package etcd
import ( import (
"encoding/json"
"net/http"
"time" "time"
) )
// The response object from the server. const (
rawResponse = iota
normalResponse
)
type responseType int
type RawResponse struct {
StatusCode int
Body []byte
Header http.Header
}
func (rr *RawResponse) toResponse() (*Response, error) {
if rr.StatusCode == http.StatusBadRequest {
return nil, handleError(rr.Body)
}
resp := new(Response)
err := json.Unmarshal(rr.Body, resp)
if err != nil {
return nil, err
}
return resp, nil
}
type Response struct { type Response struct {
Action string `json:"action"` Action string `json:"action"`
Key string `json:"key"` Node *Node `json:"node,omitempty"`
Dir bool `json:"dir,omitempty"`
PrevValue string `json:"prevValue,omitempty"`
Value string `json:"value,omitempty"`
Kvs kvPairs `json:"kvs,omitempty"`
// If the key did not exist before the action,
// this field should be set to true
NewKey bool `json:"newKey,omitempty"`
Expiration *time.Time `json:"expiration,omitempty"`
// Time to live in second
TTL int64 `json:"ttl,omitempty"`
// The command index of the raft machine when the command is executed
ModifiedIndex uint64 `json:"modifiedIndex"`
} }
// When user list a directory, we add all the node into key-value pair slice type Node struct {
type KeyValuePair struct { Key string `json:"key, omitempty"`
Key string `json:"key, omitempty"` PrevValue string `json:"prevValue,omitempty"`
Value string `json:"value,omitempty"` Value string `json:"value,omitempty"`
Dir bool `json:"dir,omitempty"` Dir bool `json:"dir,omitempty"`
KVPairs kvPairs `json:"kvs,omitempty"` Expiration *time.Time `json:"expiration,omitempty"`
TTL int64 `json:"ttl,omitempty"` TTL int64 `json:"ttl,omitempty"`
Nodes Nodes `json:"nodes,omitempty"`
ModifiedIndex uint64 `json:"modifiedIndex,omitempty"`
CreatedIndex uint64 `json:"createdIndex,omitempty"`
} }
type kvPairs []KeyValuePair type Nodes []Node
// interfaces for sorting // interfaces for sorting
func (kvs kvPairs) Len() int { func (ns Nodes) Len() int {
return len(kvs) return len(ns)
} }
func (kvs kvPairs) Less(i, j int) bool { func (ns Nodes) Less(i, j int) bool {
return kvs[i].Key < kvs[j].Key return ns[i].Key < ns[j].Key
} }
func (kvs kvPairs) Swap(i, j int) { func (ns Nodes) Swap(i, j int) {
kvs[i], kvs[j] = kvs[j], kvs[i] ns[i], ns[j] = ns[j], ns[i]
} }

View File

@ -7,12 +7,11 @@ import (
func TestSetCurlChan(t *testing.T) { func TestSetCurlChan(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { c.OpenCURL()
c.DeleteAll("foo")
}()
curlChan := make(chan string, 1) defer func() {
SetCurlChan(curlChan) c.Delete("foo", true)
}()
_, err := c.Set("foo", "bar", 5) _, err := c.Set("foo", "bar", 5)
if err != nil { if err != nil {
@ -21,21 +20,21 @@ func TestSetCurlChan(t *testing.T) {
expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5", expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5",
c.cluster.Leader) c.cluster.Leader)
actual := <-curlChan actual := c.RecvCURL()
if expected != actual { if expected != actual {
t.Fatalf(`Command "%s" is not equal to expected value "%s"`, t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
actual, expected) actual, expected)
} }
c.SetConsistency(STRONG_CONSISTENCY) c.SetConsistency(STRONG_CONSISTENCY)
_, err = c.Get("foo", false) _, err = c.Get("foo", false, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&sorted=false", expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&recursive=false&sorted=false",
c.cluster.Leader) c.cluster.Leader)
actual = <-curlChan actual = c.RecvCURL()
if expected != actual { if expected != actual {
t.Fatalf(`Command "%s" is not equal to expected value "%s"`, t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
actual, expected) actual, expected)

View File

@ -2,42 +2,110 @@ package etcd
// SetDir sets the given key to a directory. // SetDir sets the given key to a directory.
func (c *Client) SetDir(key string, ttl uint64) (*Response, error) { func (c *Client) SetDir(key string, ttl uint64) (*Response, error) {
return c.put(key, "", ttl, nil) raw, err := c.RawSetDir(key, ttl)
if err != nil {
return nil, err
}
return raw.toResponse()
} }
// UpdateDir updates the given key to a directory. It succeeds only if the // UpdateDir updates the given key to a directory. It succeeds only if the
// given key already exists. // given key already exists.
func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) { func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) {
return c.put(key, "", ttl, options{ raw, err := c.RawUpdateDir(key, ttl)
"prevExist": true,
}) if err != nil {
return nil, err
}
return raw.toResponse()
} }
// UpdateDir creates a directory under the given key. It succeeds only if // UpdateDir creates a directory under the given key. It succeeds only if
// the given key does not yet exist. // the given key does not yet exist.
func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) { func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) {
return c.put(key, "", ttl, options{ raw, err := c.RawCreateDir(key, ttl)
"prevExist": false,
}) if err != nil {
return nil, err
}
return raw.toResponse()
} }
// Set sets the given key to the given value. // Set sets the given key to the given value.
func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) { func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) {
return c.put(key, value, ttl, nil) raw, err := c.RawSet(key, value, ttl)
if err != nil {
return nil, err
}
return raw.toResponse()
} }
// Update updates the given key to the given value. It succeeds only if the // Update updates the given key to the given value. It succeeds only if the
// given key already exists. // given key already exists.
func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) { func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) {
return c.put(key, value, ttl, options{ raw, err := c.RawUpdate(key, value, ttl)
"prevExist": true,
}) if err != nil {
return nil, err
}
return raw.toResponse()
} }
// Create creates a file with the given value under the given key. It succeeds // Create creates a file with the given value under the given key. It succeeds
// only if the given key does not yet exist. // only if the given key does not yet exist.
func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) { func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) {
return c.put(key, value, ttl, options{ raw, err := c.RawCreate(key, value, ttl)
"prevExist": false,
}) if err != nil {
return nil, err
}
return raw.toResponse()
}
func (c *Client) RawSetDir(key string, ttl uint64) (*RawResponse, error) {
return c.put(key, "", ttl, nil)
}
func (c *Client) RawUpdateDir(key string, ttl uint64) (*RawResponse, error) {
ops := options{
"prevExist": true,
}
return c.put(key, "", ttl, ops)
}
func (c *Client) RawCreateDir(key string, ttl uint64) (*RawResponse, error) {
ops := options{
"prevExist": false,
}
return c.put(key, "", ttl, ops)
}
func (c *Client) RawSet(key string, value string, ttl uint64) (*RawResponse, error) {
return c.put(key, value, ttl, nil)
}
func (c *Client) RawUpdate(key string, value string, ttl uint64) (*RawResponse, error) {
ops := options{
"prevExist": true,
}
return c.put(key, value, ttl, ops)
}
func (c *Client) RawCreate(key string, value string, ttl uint64) (*RawResponse, error) {
ops := options{
"prevExist": false,
}
return c.put(key, value, ttl, ops)
} }

View File

@ -7,14 +7,14 @@ import (
func TestSet(t *testing.T) { func TestSet(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("foo") c.Delete("foo", true)
}() }()
resp, err := c.Set("foo", "bar", 5) resp, err := c.Set("foo", "bar", 5)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if resp.Key != "/foo" || resp.Value != "bar" || resp.TTL != 5 { if resp.Node.Key != "/foo" || resp.Node.Value != "bar" || resp.Node.TTL != 5 {
t.Fatalf("Set 1 failed: %#v", resp) t.Fatalf("Set 1 failed: %#v", resp)
} }
@ -22,8 +22,8 @@ func TestSet(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Key == "/foo" && resp.Value == "bar2" && if !(resp.Node.Key == "/foo" && resp.Node.Value == "bar2" &&
resp.PrevValue == "bar" && resp.TTL == 5) { resp.Node.PrevValue == "bar" && resp.Node.TTL == 5) {
t.Fatalf("Set 2 failed: %#v", resp) t.Fatalf("Set 2 failed: %#v", resp)
} }
} }
@ -31,12 +31,12 @@ func TestSet(t *testing.T) {
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("foo") c.Delete("foo", true)
c.DeleteAll("nonexistent") c.Delete("nonexistent", true)
}() }()
resp, err := c.Set("foo", "bar", 5) resp, err := c.Set("foo", "bar", 5)
t.Logf("%#v", resp)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -47,8 +47,8 @@ func TestUpdate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Action == "update" && resp.Key == "/foo" && if !(resp.Action == "update" && resp.Node.Key == "/foo" &&
resp.PrevValue == "bar" && resp.TTL == 5) { resp.Node.PrevValue == "bar" && resp.Node.TTL == 5) {
t.Fatalf("Update 1 failed: %#v", resp) t.Fatalf("Update 1 failed: %#v", resp)
} }
@ -56,14 +56,14 @@ func TestUpdate(t *testing.T) {
resp, err = c.Update("nonexistent", "whatever", 5) resp, err = c.Update("nonexistent", "whatever", 5)
if err == nil { if err == nil {
t.Fatalf("The key %v did not exist, so the update should have failed."+ t.Fatalf("The key %v did not exist, so the update should have failed."+
"The response was: %#v", resp.Key, resp) "The response was: %#v", resp.Node.Key, resp)
} }
} }
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("newKey") c.Delete("newKey", true)
}() }()
newKey := "/newKey" newKey := "/newKey"
@ -75,8 +75,8 @@ func TestCreate(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Action == "create" && resp.Key == newKey && if !(resp.Action == "create" && resp.Node.Key == newKey &&
resp.Value == newValue && resp.PrevValue == "" && resp.TTL == 5) { resp.Node.Value == newValue && resp.Node.PrevValue == "" && resp.Node.TTL == 5) {
t.Fatalf("Create 1 failed: %#v", resp) t.Fatalf("Create 1 failed: %#v", resp)
} }
@ -84,22 +84,22 @@ func TestCreate(t *testing.T) {
resp, err = c.Create(newKey, newValue, 5) resp, err = c.Create(newKey, newValue, 5)
if err == nil { if err == nil {
t.Fatalf("The key %v did exist, so the creation should have failed."+ t.Fatalf("The key %v did exist, so the creation should have failed."+
"The response was: %#v", resp.Key, resp) "The response was: %#v", resp.Node.Key, resp)
} }
} }
func TestSetDir(t *testing.T) { func TestSetDir(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("foo") c.Delete("foo", true)
c.DeleteAll("fooDir") c.Delete("fooDir", true)
}() }()
resp, err := c.SetDir("fooDir", 5) resp, err := c.SetDir("fooDir", 5)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Key == "/fooDir" && resp.Value == "" && resp.TTL == 5) { if !(resp.Node.Key == "/fooDir" && resp.Node.Value == "" && resp.Node.TTL == 5) {
t.Fatalf("SetDir 1 failed: %#v", resp) t.Fatalf("SetDir 1 failed: %#v", resp)
} }
@ -120,8 +120,8 @@ func TestSetDir(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Key == "/foo" && resp.Value == "" && if !(resp.Node.Key == "/foo" && resp.Node.Value == "" &&
resp.PrevValue == "bar" && resp.TTL == 5) { resp.Node.PrevValue == "bar" && resp.Node.TTL == 5) {
t.Fatalf("SetDir 2 failed: %#v", resp) t.Fatalf("SetDir 2 failed: %#v", resp)
} }
} }
@ -129,11 +129,10 @@ func TestSetDir(t *testing.T) {
func TestUpdateDir(t *testing.T) { func TestUpdateDir(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("fooDir") c.Delete("fooDir", true)
}() }()
resp, err := c.SetDir("fooDir", 5) resp, err := c.SetDir("fooDir", 5)
t.Logf("%#v", resp)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -144,8 +143,8 @@ func TestUpdateDir(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Action == "update" && resp.Key == "/fooDir" && if !(resp.Action == "update" && resp.Node.Key == "/fooDir" &&
resp.Value == "" && resp.PrevValue == "" && resp.TTL == 5) { resp.Node.Value == "" && resp.Node.PrevValue == "" && resp.Node.TTL == 5) {
t.Fatalf("UpdateDir 1 failed: %#v", resp) t.Fatalf("UpdateDir 1 failed: %#v", resp)
} }
@ -153,14 +152,14 @@ func TestUpdateDir(t *testing.T) {
resp, err = c.UpdateDir("nonexistentDir", 5) resp, err = c.UpdateDir("nonexistentDir", 5)
if err == nil { if err == nil {
t.Fatalf("The key %v did not exist, so the update should have failed."+ t.Fatalf("The key %v did not exist, so the update should have failed."+
"The response was: %#v", resp.Key, resp) "The response was: %#v", resp.Node.Key, resp)
} }
} }
func TestCreateDir(t *testing.T) { func TestCreateDir(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("fooDir") c.Delete("fooDir", true)
}() }()
// This should succeed // This should succeed
@ -169,8 +168,8 @@ func TestCreateDir(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Action == "create" && resp.Key == "/fooDir" && if !(resp.Action == "create" && resp.Node.Key == "/fooDir" &&
resp.Value == "" && resp.PrevValue == "" && resp.TTL == 5) { resp.Node.Value == "" && resp.Node.PrevValue == "" && resp.Node.TTL == 5) {
t.Fatalf("CreateDir 1 failed: %#v", resp) t.Fatalf("CreateDir 1 failed: %#v", resp)
} }
@ -178,6 +177,6 @@ func TestCreateDir(t *testing.T) {
resp, err = c.CreateDir("fooDir", 5) resp, err = c.CreateDir("fooDir", 5)
if err == nil { if err == nil {
t.Fatalf("The key %v did exist, so the creation should have failed."+ t.Fatalf("The key %v did exist, so the creation should have failed."+
"The response was: %#v", resp.Key, resp) "The response was: %#v", resp.Node.Key, resp)
} }
} }

View File

@ -9,44 +9,74 @@ var (
ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel") ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel")
) )
// WatchAll returns the first change under the given prefix since the given index. To // If recursive is set to true the watch returns the first change under the given
// watch for the latest change, set waitIndex = 0. // prefix since the given index.
// //
// If the prefix points to a directory, any change under it, including all child directories, // If recursive is set to false the watch returns the first change to the given key
// will be returned. // since the given index.
//
// To watch for the latest change, set waitIndex = 0.
// //
// If a receiver channel is given, it will be a long-term watch. Watch will block at the // If a receiver channel is given, it will be a long-term watch. Watch will block at the
// channel. And after someone receive the channel, it will go on to watch that prefix. //channel. After someone receives the channel, it will go on to watch that
// If a stop channel is given, client can close long-term watch using the stop channel // prefix. If a stop channel is given, the client can close long-term watch using
func (c *Client) WatchAll(prefix string, waitIndex uint64, receiver chan *Response, stop chan bool) (*Response, error) { // the stop channel.
return c.watch(prefix, waitIndex, true, receiver, stop) func (c *Client) Watch(prefix string, waitIndex uint64, recursive bool,
} receiver chan *Response, stop chan bool) (*Response, error) {
// Watch returns the first change to the given key since the given index. To
// watch for the latest change, set waitIndex = 0.
//
// If a receiver channel is given, it will be a long-term watch. Watch will block at the
// channel. And after someone receive the channel, it will go on to watch that
// prefix. If a stop channel is given, client can close long-term watch using
// the stop channel
func (c *Client) Watch(key string, waitIndex uint64, receiver chan *Response, stop chan bool) (*Response, error) {
return c.watch(key, waitIndex, false, receiver, stop)
}
func (c *Client) watch(prefix string, waitIndex uint64, recursive bool, receiver chan *Response, stop chan bool) (*Response, error) {
logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader) logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader)
if receiver == nil { if receiver == nil {
return c.watchOnce(prefix, waitIndex, recursive, stop) raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
} else {
for { if err != nil {
resp, err := c.watchOnce(prefix, waitIndex, recursive, stop) return nil, err
if resp != nil {
waitIndex = resp.ModifiedIndex + 1
receiver <- resp
} else {
return nil, err
}
} }
return raw.toResponse()
}
for {
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
if err != nil {
return nil, err
}
resp, err := raw.toResponse()
if err != nil {
return nil, err
}
waitIndex = resp.Node.ModifiedIndex + 1
receiver <- resp
}
return nil, nil
}
func (c *Client) RawWatch(prefix string, waitIndex uint64, recursive bool,
receiver chan *RawResponse, stop chan bool) (*RawResponse, error) {
logger.Debugf("rawWatch %s [%s]", prefix, c.cluster.Leader)
if receiver == nil {
return c.watchOnce(prefix, waitIndex, recursive, stop)
}
for {
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
if err != nil {
return nil, err
}
resp, err := raw.toResponse()
if err != nil {
return nil, err
}
waitIndex = resp.Node.ModifiedIndex + 1
receiver <- raw
} }
return nil, nil return nil, nil
@ -54,9 +84,9 @@ func (c *Client) watch(prefix string, waitIndex uint64, recursive bool, receiver
// helper func // helper func
// return when there is change under the given prefix // return when there is change under the given prefix
func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*Response, error) { func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*RawResponse, error) {
respChan := make(chan *Response) respChan := make(chan *RawResponse, 1)
errChan := make(chan error) errChan := make(chan error)
go func() { go func() {
@ -74,6 +104,7 @@ func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop ch
if err != nil { if err != nil {
errChan <- err errChan <- err
return
} }
respChan <- resp respChan <- resp

View File

@ -9,26 +9,26 @@ import (
func TestWatch(t *testing.T) { func TestWatch(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("watch_foo") c.Delete("watch_foo", true)
}() }()
go setHelper("watch_foo", "bar", c) go setHelper("watch_foo", "bar", c)
resp, err := c.Watch("watch_foo", 0, nil, nil) resp, err := c.Watch("watch_foo", 0, false, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Key == "/watch_foo" && resp.Value == "bar") { if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
t.Fatalf("Watch 1 failed: %#v", resp) t.Fatalf("Watch 1 failed: %#v", resp)
} }
go setHelper("watch_foo", "bar", c) go setHelper("watch_foo", "bar", c)
resp, err = c.Watch("watch_foo", resp.ModifiedIndex, nil, nil) resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, false, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Key == "/watch_foo" && resp.Value == "bar") { if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
t.Fatalf("Watch 2 failed: %#v", resp) t.Fatalf("Watch 2 failed: %#v", resp)
} }
@ -39,7 +39,7 @@ func TestWatch(t *testing.T) {
go receiver(ch, stop) go receiver(ch, stop)
_, err = c.Watch("watch_foo", 0, ch, stop) _, err = c.Watch("watch_foo", 0, false, ch, stop)
if err != ErrWatchStoppedByUser { if err != ErrWatchStoppedByUser {
t.Fatalf("Watch returned a non-user stop error") t.Fatalf("Watch returned a non-user stop error")
} }
@ -48,26 +48,26 @@ func TestWatch(t *testing.T) {
func TestWatchAll(t *testing.T) { func TestWatchAll(t *testing.T) {
c := NewClient(nil) c := NewClient(nil)
defer func() { defer func() {
c.DeleteAll("watch_foo") c.Delete("watch_foo", true)
}() }()
go setHelper("watch_foo/foo", "bar", c) go setHelper("watch_foo/foo", "bar", c)
resp, err := c.WatchAll("watch_foo", 0, nil, nil) resp, err := c.Watch("watch_foo", 0, true, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Key == "/watch_foo/foo" && resp.Value == "bar") { if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
t.Fatalf("WatchAll 1 failed: %#v", resp) t.Fatalf("WatchAll 1 failed: %#v", resp)
} }
go setHelper("watch_foo/foo", "bar", c) go setHelper("watch_foo/foo", "bar", c)
resp, err = c.WatchAll("watch_foo", resp.ModifiedIndex, nil, nil) resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, true, nil, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !(resp.Key == "/watch_foo/foo" && resp.Value == "bar") { if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
t.Fatalf("WatchAll 2 failed: %#v", resp) t.Fatalf("WatchAll 2 failed: %#v", resp)
} }
@ -78,7 +78,7 @@ func TestWatchAll(t *testing.T) {
go receiver(ch, stop) go receiver(ch, stop)
_, err = c.WatchAll("watch_foo", 0, ch, stop) _, err = c.Watch("watch_foo", 0, true, ch, stop)
if err != ErrWatchStoppedByUser { if err != ErrWatchStoppedByUser {
t.Fatalf("Watch returned a non-user stop error") t.Fatalf("Watch returned a non-user stop error")
} }

View File

@ -1,5 +1,6 @@
context context
======= =======
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
gorilla/context is a general purpose registry for global request variables. gorilla/context is a general purpose registry for global request variables.

View File

@ -92,7 +92,7 @@ func Purge(maxAge int) int {
datat = make(map[*http.Request]int64) datat = make(map[*http.Request]int64)
} else { } else {
min := time.Now().Unix() - int64(maxAge) min := time.Now().Unix() - int64(maxAge)
for r, _ := range data { for r := range data {
if datat[r] < min { if datat[r] < min {
clear(r) clear(r)
count++ count++

View File

@ -1,5 +1,6 @@
mux mux
=== ===
[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux)
gorilla/mux is a powerful URL router and dispatcher. gorilla/mux is a powerful URL router and dispatcher.

View File

@ -67,6 +67,14 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Clean path to canonical form and redirect. // Clean path to canonical form and redirect.
if p := cleanPath(req.URL.Path); p != req.URL.Path { if p := cleanPath(req.URL.Path); p != req.URL.Path {
// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
// http://code.google.com/p/go/issues/detail?id=5252
url := *req.URL
url.Path = p
p = url.String()
w.Header().Set("Location", p) w.Header().Set("Location", p)
w.WriteHeader(http.StatusMovedPermanently) w.WriteHeader(http.StatusMovedPermanently)
return return

View File

@ -22,6 +22,7 @@ type routeTest struct {
shouldMatch bool // whether the request is expected to match the route at all shouldMatch bool // whether the request is expected to match the route at all
} }
func TestHost(t *testing.T) { func TestHost(t *testing.T) {
// newRequestHost a new request with a method, url, and host header // newRequestHost a new request with a method, url, and host header
newRequestHost := func(method, url, host string) *http.Request { newRequestHost := func(method, url, host string) *http.Request {
@ -416,6 +417,15 @@ func TestQueries(t *testing.T) {
path: "", path: "",
shouldMatch: true, shouldMatch: true,
}, },
{
title: "Queries route, match with a query string",
route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
vars: map[string]string{},
host: "",
path: "",
shouldMatch: true,
},
{ {
title: "Queries route, bad query", title: "Queries route, bad query",
route: new(Route).Queries("foo", "bar", "baz", "ding"), route: new(Route).Queries("foo", "bar", "baz", "ding"),
@ -663,7 +673,7 @@ func testRoute(t *testing.T, test routeTest) {
func TestKeepContext(t *testing.T) { func TestKeepContext(t *testing.T) {
func1 := func(w http.ResponseWriter, r *http.Request) {} func1 := func(w http.ResponseWriter, r *http.Request) {}
r := NewRouter() r:= NewRouter()
r.HandleFunc("/", func1).Name("func1") r.HandleFunc("/", func1).Name("func1")
req, _ := http.NewRequest("GET", "http://localhost/", nil) req, _ := http.NewRequest("GET", "http://localhost/", nil)
@ -688,6 +698,47 @@ func TestKeepContext(t *testing.T) {
} }
type TestA301ResponseWriter struct {
hh http.Header
status int
}
func (ho TestA301ResponseWriter) Header() http.Header {
return http.Header(ho.hh)
}
func (ho TestA301ResponseWriter) Write( b []byte) (int, error) {
return 0, nil
}
func (ho TestA301ResponseWriter) WriteHeader( code int ) {
ho.status = code
}
func Test301Redirect(t *testing.T) {
m := make(http.Header)
func1 := func(w http.ResponseWriter, r *http.Request) {}
func2 := func(w http.ResponseWriter, r *http.Request) {}
r:= NewRouter()
r.HandleFunc("/api/", func2).Name("func2")
r.HandleFunc("/", func1).Name("func1")
req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
res := TestA301ResponseWriter{
hh: m,
status : 0,
}
r.ServeHTTP(&res, req)
if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
t.Errorf("Should have complete URL with query string")
}
}
// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW // https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
func TestSubrouterHeader(t *testing.T) { func TestSubrouterHeader(t *testing.T) {
expected := "func1 response" expected := "func1 response"

View File

@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) {
method = "GET" method = "GET"
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
resultVars = map[bool]map[string]string{ resultVars = map[bool]map[string]string{
true: map[string]string{"var1": "www", "var2": "product", "var3": "42"}, true: {"var1": "www", "var2": "product", "var3": "42"},
false: map[string]string{}, false: {},
} }
} }
@ -110,8 +110,8 @@ func TestRouteMatchers(t *testing.T) {
method = "POST" method = "POST"
headers = map[string]string{"Content-Type": "application/json"} headers = map[string]string{"Content-Type": "application/json"}
resultVars = map[bool]map[string]string{ resultVars = map[bool]map[string]string{
true: map[string]string{"var4": "google", "var5": "product", "var6": "42"}, true: {"var4": "google", "var5": "product", "var6": "42"},
false: map[string]string{}, false: {},
} }
} }