HTRACE-53. Move client code into client.go, add unit tests for bin/htrace command and htraced REST API (cmccabe)
diff --git a/htrace-core/src/go/src/org/apache/htrace/client/client.go b/htrace-core/src/go/src/org/apache/htrace/client/client.go
new file mode 100644
index 0000000..fbcdcc6
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/client/client.go
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"org/apache/htrace/common"
+	"org/apache/htrace/conf"
+)
+
+// A golang client for htraced.
+// TODO: fancier APIs for streaming spans in the background, optimize TCP stuff
+
+func NewClient(cnf *conf.Config) (*Client, error) {
+	hcl := Client{restAddr: cnf.Get(conf.HTRACE_WEB_ADDRESS)}
+	return &hcl, nil
+}
+
+type Client struct {
+	// REST address of the htraced server.
+	restAddr string
+}
+
+// Get the htraced server information.
+func (hcl *Client) GetServerInfo() (*common.ServerInfo, error) {
+	buf, _, err := hcl.makeGetRequest("server/info")
+	if err != nil {
+		return nil, err
+	}
+	var info common.ServerInfo
+	err = json.Unmarshal(buf, &info)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Error: error unmarshalling response "+
+			"body %s: %s", string(buf), err.Error()))
+	}
+	return &info, nil
+}
+
+// Get information about a trace span.  Returns nil, nil if the span was not found.
+func (hcl *Client) FindSpan(sid common.SpanId) (*common.Span, error) {
+	buf, rc, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x", uint64(sid)))
+	if err != nil {
+		if rc == http.StatusNoContent {
+			return nil, nil
+		}
+		return nil, err
+	}
+	var span common.Span
+	err = json.Unmarshal(buf, &span)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Error unmarshalling response "+
+			"body %s: %s", string(buf), err.Error()))
+	}
+	return &span, nil
+}
+
+func (hcl *Client) WriteSpan(span *common.Span) error {
+	buf, err := json.Marshal(span)
+	if err != nil {
+		return err
+	}
+	_, _, err = hcl.makeRestRequest("POST", "writeSpans", bytes.NewReader(buf))
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// Find the child IDs of a given span ID.
+// TODO: add offset as well as limit?
+func (hcl *Client) FindChildren(sid common.SpanId, lim int) ([]common.SpanId, error) {
+	buf, _, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x/children?lim=%d",
+		uint64(sid), lim))
+	if err != nil {
+		return nil, err
+	}
+	var spanIds []common.SpanId
+	err = json.Unmarshal(buf, &spanIds)
+	if err != nil {
+		return nil, errors.New(fmt.Sprintf("Error: error unmarshalling response "+
+			"body %s: %s", string(buf), err.Error()))
+	}
+	return spanIds, nil
+}
+
+func (hcl *Client) makeGetRequest(reqName string) ([]byte, int, error) {
+	return hcl.makeRestRequest("GET", reqName, nil)
+}
+
+// Make a general JSON REST request.
+// Returns the request body, the response code, and the error.
+// Note: if the response code is non-zero, the error will also be non-zero.
+func (hcl *Client) makeRestRequest(reqType string, reqName string, reqBody io.Reader) ([]byte, int, error) {
+	url := fmt.Sprintf("http://%s/%s", hcl.restAddr, reqName)
+	req, err := http.NewRequest(reqType, url, reqBody)
+	req.Header.Set("Content-Type", "application/json")
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, -1, errors.New(fmt.Sprintf("Error: error making http request to %s: %s\n", url,
+			err.Error()))
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return nil, resp.StatusCode,
+			errors.New(fmt.Sprintf("Error: got bad response status from %s: %s\n", url, resp.Status))
+	}
+	var body []byte
+	body, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, -1, errors.New(fmt.Sprintf("Error: error reading response body: %s\n", err.Error()))
+	}
+	return body, 0, nil
+}
diff --git a/htrace-core/src/go/src/org/apache/htrace/common/span.go b/htrace-core/src/go/src/org/apache/htrace/common/span.go
index 20d3e45..36e716a 100644
--- a/htrace-core/src/go/src/org/apache/htrace/common/span.go
+++ b/htrace-core/src/go/src/org/apache/htrace/common/span.go
@@ -57,6 +57,20 @@
 	return []byte(`"` + fmt.Sprintf("%016x", uint64(id)) + `"`), nil
 }
 
+type SpanIdSlice []SpanId
+
+func (s SpanIdSlice) Len() int {
+	return len(s)
+}
+
+func (s SpanIdSlice) Less(i, j int) bool {
+	return s[i] < s[j]
+}
+
+func (s SpanIdSlice) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
 const DOUBLE_QUOTE = 0x22
 
 func (id *SpanId) UnmarshalJSON(b []byte) error {
diff --git a/htrace-core/src/go/src/org/apache/htrace/conf/config.go b/htrace-core/src/go/src/org/apache/htrace/conf/config.go
index 528d6c1..41e39fa 100644
--- a/htrace-core/src/go/src/org/apache/htrace/conf/config.go
+++ b/htrace-core/src/go/src/org/apache/htrace/conf/config.go
@@ -42,6 +42,9 @@
 // For that reason, it is not necessary for the Get, GetInt, etc. functions to take a default value
 // argument.
 //
+// Configuration objects are immutable.  However, you can make a copy of a configuration which adds
+// some changes using Configuration#Clone().
+//
 
 type Config struct {
 	settings map[string]string
@@ -64,7 +67,7 @@
 
 // Load a configuration from the application's argv, configuration file, and the standard
 // defaults.
-func LoadApplicationConfig() *Config {
+func LoadApplicationConfig(values map[string]string) *Config {
 	reader, err := openFile(CONFIG_FILE_NAME, []string{"."})
 	if err != nil {
 		log.Fatal("Error opening config file: " + err.Error())
@@ -76,6 +79,9 @@
 	}
 	bld.Argv = os.Args[1:]
 	bld.Defaults = DEFAULTS
+	if values != nil {
+		bld.Values = values
+	}
 	var cnf *Config
 	cnf, err = bld.Build()
 	if err != nil {
@@ -199,3 +205,23 @@
 	}
 	return 0
 }
+
+// Make a deep copy of the given configuration.
+// Optionally, you can specify particular key/value pairs to change.
+// Example:
+// cnf2 := cnf.Copy("my.changed.key", "my.new.value")
+func (cnf *Config) Clone(args ...string) *Config {
+	if len(args)%2 != 0 {
+		panic("The arguments to Config#copy are key1, value1, " +
+			"key2, value2, and so on.  You must specify an even number of arguments.")
+	}
+	ncnf := &Config{defaults: cnf.defaults}
+	ncnf.settings = make(map[string]string)
+	for k, v := range cnf.settings {
+		ncnf.settings[k] = v
+	}
+	for i := 0; i < len(args); i += 2 {
+		ncnf.settings[args[i]] = args[i+1]
+	}
+	return ncnf
+}
diff --git a/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go b/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go
index c7318c1..8539914 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htrace/cmd.go
@@ -20,14 +20,12 @@
 package main
 
 import (
-	"bytes"
+	"bufio"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"github.com/alecthomas/kingpin"
 	"io"
-	"io/ioutil"
-	"net/http"
+	htrace "org/apache/htrace/client"
 	"org/apache/htrace/common"
 	"org/apache/htrace/conf"
 	"os"
@@ -36,14 +34,17 @@
 var RELEASE_VERSION string
 var GIT_VERSION string
 
-func main() {
-	// Load htraced configuration
-	cnf := conf.LoadApplicationConfig()
+const EXIT_SUCCESS = 0
+const EXIT_FAILURE = 1
 
+var verbose *bool
+
+func main() {
 	// Parse argv
 	app := kingpin.New("htrace", "The HTrace tracing utility.")
 	addr := app.Flag("addr", "Server address.").
-		Default(cnf.Get(conf.HTRACE_WEB_ADDRESS)).TCP()
+		Default(conf.DEFAULTS[conf.HTRACE_WEB_ADDRESS]).TCP()
+	verbose = app.Flag("verbose", "Verbose.").Default("false").Bool()
 	version := app.Command("version", "Print the version of this program.")
 	serverInfo := app.Command("serverInfo", "Print information retrieved from an htraced server.")
 	findSpan := app.Command("findSpan", "Print information about a trace span with a given ID.")
@@ -54,20 +55,46 @@
 		"number").Required().Uint64()
 	childLim := findChildren.Flag("lim", "Maximum number of child IDs to print.").Default("20").Int()
 	writeSpans := app.Command("writeSpans", "Write spans to the server in JSON form.")
-	spanJson := writeSpans.Flag("json", "The JSON span data to write to the server.").Required().String()
+	spanJson := writeSpans.Flag("json", "The JSON span data to write to the server.").String()
+	spanFile := writeSpans.Flag("file",
+		"A file containing JSON span data to write to the server.").String()
+	cmd := kingpin.MustParse(app.Parse(os.Args[1:]))
+
+	// Load htraced configuration
+	values := make(map[string]string)
+	values[conf.HTRACE_WEB_ADDRESS] = (*addr).String()
+	cnf := conf.LoadApplicationConfig(values)
+
+	// Create HTrace client
+	hcl, err := htrace.NewClient(cnf)
+	if err != nil {
+		fmt.Printf("Failed to create HTrace client: %s\n", err.Error())
+		os.Exit(EXIT_FAILURE)
+	}
 
 	// Handle operation
-	switch kingpin.MustParse(app.Parse(os.Args[1:])) {
+	switch cmd {
 	case version.FullCommand():
 		os.Exit(printVersion())
 	case serverInfo.FullCommand():
-		os.Exit(printServerInfo((*addr).String()))
+		os.Exit(printServerInfo(hcl))
 	case findSpan.FullCommand():
-		os.Exit(doFindSpan((*addr).String(), *findSpanId))
+		os.Exit(doFindSpan(hcl, common.SpanId(*findSpanId)))
 	case findChildren.FullCommand():
-		os.Exit(doFindChildren((*addr).String(), *parentSpanId, *childLim))
+		os.Exit(doFindChildren(hcl, common.SpanId(*parentSpanId), *childLim))
 	case writeSpans.FullCommand():
-		os.Exit(doWriteSpans((*addr).String(), *spanJson))
+		if *spanJson != "" {
+			if *spanFile != "" {
+				fmt.Printf("You must specify either --json or --file, " +
+					"but not both.\n")
+				os.Exit(EXIT_FAILURE)
+			}
+			os.Exit(doWriteSpanJson(hcl, *spanJson))
+		} else if *spanFile != "" {
+			os.Exit(doWriteSpanJsonFile(hcl, *spanFile))
+		}
+		fmt.Printf("You must specify either --json or --file.\n")
+		os.Exit(EXIT_FAILURE)
 	}
 
 	app.UsageErrorf(os.Stderr, "You must supply a command to run.")
@@ -76,73 +103,91 @@
 // Print the version of the htrace binary.
 func printVersion() int {
 	fmt.Printf("Running htrace command version %s.\n", RELEASE_VERSION)
-	return 0
+	return EXIT_SUCCESS
 }
 
 // Print information retrieved from an htraced server via /server/info
-func printServerInfo(restAddr string) int {
-	buf, err := makeGetRequest(restAddr, "server/info")
+func printServerInfo(hcl *htrace.Client) int {
+	info, err := hcl.GetServerInfo()
 	if err != nil {
-		fmt.Printf("%s\n", err.Error())
-		return 1
-	}
-	var info common.ServerInfo
-	err = json.Unmarshal(buf, &info)
-	if err != nil {
-		fmt.Printf("Error: error unmarshalling response body %s: %s\n",
-			string(buf), err.Error())
-		return 1
+		fmt.Println(err.Error())
+		return EXIT_FAILURE
 	}
 	fmt.Printf("HTraced server version %s (%s)\n", info.ReleaseVersion, info.GitVersion)
-	return 0
+	return EXIT_SUCCESS
 }
 
 // Print information about a trace span.
-func doFindSpan(restAddr string, sid uint64) int {
-	buf, err := makeGetRequest(restAddr, fmt.Sprintf("span/%016x", sid))
+func doFindSpan(hcl *htrace.Client, sid common.SpanId) int {
+	span, err := hcl.FindSpan(sid)
 	if err != nil {
-		fmt.Printf("%s\n", err.Error())
-		return 1
+		fmt.Println(err.Error())
+		return EXIT_FAILURE
 	}
-	var span common.Span
-	err = json.Unmarshal(buf, &span)
-	if err != nil {
-		fmt.Printf("Error: error unmarshalling response body %s: %s\n",
-			string(buf), err.Error())
-		return 1
+	if span == nil {
+		fmt.Printf("Span ID not found.\n")
+		return EXIT_FAILURE
 	}
 	pbuf, err := json.MarshalIndent(span, "", "  ")
 	if err != nil {
-		fmt.Println("Error: error pretty-printing span to JSON: %s", err.Error())
-		return 1
+		fmt.Printf("Error: error pretty-printing span to JSON: %s\n", err.Error())
+		return EXIT_FAILURE
 	}
 	fmt.Printf("%s\n", string(pbuf))
-	return 0
+	return EXIT_SUCCESS
 }
 
-func doWriteSpans(restAddr string, spanJson string) int {
-	body := []byte(spanJson)
-	_, err := makeRestRequest("POST", restAddr, "writeSpans", bytes.NewReader(body))
+func doWriteSpanJsonFile(hcl *htrace.Client, spanFile string) int {
+	file, err := os.Open(spanFile)
 	if err != nil {
-		fmt.Printf("%s\n", err.Error())
-		return 1
+		fmt.Printf("Failed to open %s: %s\n", spanFile, err.Error())
+		return EXIT_FAILURE
 	}
-	return 0
+	defer file.Close()
+	in := bufio.NewReader(file)
+	dec := json.NewDecoder(in)
+	for {
+		var span common.Span
+		if err = dec.Decode(&span); err != nil {
+			if err == io.EOF {
+				break
+			}
+			fmt.Println("Failed to decode JSON: %s", err.Error())
+			return EXIT_FAILURE
+		}
+		if *verbose {
+			fmt.Printf("wrote %s\n", span.ToJson())
+		}
+		if err = hcl.WriteSpan(&span); err != nil {
+			fmt.Println(err.Error())
+			return EXIT_FAILURE
+		}
+	}
+	return EXIT_SUCCESS
+}
+
+func doWriteSpanJson(hcl *htrace.Client, spanJson string) int {
+	spanBytes := []byte(spanJson)
+	var span common.Span
+	err := json.Unmarshal(spanBytes, &span)
+	if err != nil {
+		fmt.Printf("Error parsing provided JSON: %s\n", err.Error())
+		return EXIT_FAILURE
+	}
+	err = hcl.WriteSpan(&span)
+	if err != nil {
+		fmt.Println(err.Error())
+		return EXIT_FAILURE
+	}
+	return EXIT_SUCCESS
 }
 
 // Find information about the children of a span.
-func doFindChildren(restAddr string, sid uint64, lim int) int {
-	buf, err := makeGetRequest(restAddr, fmt.Sprintf("span/%016x/children?lim=%d", sid, lim))
+func doFindChildren(hcl *htrace.Client, sid common.SpanId, lim int) int {
+	spanIds, err := hcl.FindChildren(sid, lim)
 	if err != nil {
 		fmt.Printf("%s\n", err.Error())
-		return 1
-	}
-	var spanIds []common.SpanId
-	err = json.Unmarshal(buf, &spanIds)
-	if err != nil {
-		fmt.Printf("Error: error unmarshalling response body %s: %s\n",
-			string(buf), err.Error())
-		return 1
+		return EXIT_FAILURE
 	}
 	pbuf, err := json.MarshalIndent(spanIds, "", "  ")
 	if err != nil {
@@ -152,31 +197,3 @@
 	fmt.Printf("%s\n", string(pbuf))
 	return 0
 }
-
-func makeGetRequest(restAddr string, reqName string) ([]byte, error) {
-	return makeRestRequest("GET", restAddr, reqName, nil)
-}
-
-// Print information retrieved from an htraced server via /serverInfo
-func makeRestRequest(reqType string, restAddr string, reqName string,
-	reqBody io.Reader) ([]byte, error) {
-	url := fmt.Sprintf("http://%s/%s", restAddr, reqName)
-	req, err := http.NewRequest(reqType, url, reqBody)
-	req.Header.Set("Content-Type", "application/json")
-	client := &http.Client{}
-	resp, err := client.Do(req)
-	if err != nil {
-		return nil, errors.New(fmt.Sprintf("Error: error making http request to %s: %s\n", url,
-			err.Error()))
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != http.StatusOK {
-		return nil, errors.New(fmt.Sprintf("Error: got bad response status from %s: %s\n", url, resp.Status))
-	}
-	var body []byte
-	body, err = ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return nil, errors.New(fmt.Sprintf("Error: error reading response body: %s\n", err.Error()))
-	}
-	return body, nil
-}
diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/client_test.go b/htrace-core/src/go/src/org/apache/htrace/htraced/client_test.go
new file mode 100644
index 0000000..15fbe89
--- /dev/null
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/client_test.go
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 main
+
+import (
+	"math/rand"
+	htrace "org/apache/htrace/client"
+	"org/apache/htrace/common"
+	"org/apache/htrace/test"
+	"testing"
+)
+
+func TestClientGetServerInfo(t *testing.T) {
+	htraceBld := &MiniHTracedBuilder{Name: "TestClientGetServerInfo", NumDataDirs: 1}
+	ht, err := htraceBld.Build()
+	if err != nil {
+		t.Fatalf("failed to create datastore: %s", err.Error())
+	}
+	defer ht.Close()
+	var hcl *htrace.Client
+	hcl, err = htrace.NewClient(ht.ClientConf())
+	if err != nil {
+		t.Fatalf("failed to create client: %s", err.Error())
+	}
+	_, err = hcl.GetServerInfo()
+	if err != nil {
+		t.Fatalf("failed to call GetServerInfo: %s", err.Error())
+	}
+}
+
+func TestClientOperations(t *testing.T) {
+	htraceBld := &MiniHTracedBuilder{Name: "TestClientOperations", NumDataDirs: 2}
+	ht, err := htraceBld.Build()
+	if err != nil {
+		t.Fatalf("failed to create datastore: %s", err.Error())
+	}
+	defer ht.Close()
+	var hcl *htrace.Client
+	hcl, err = htrace.NewClient(ht.ClientConf())
+	if err != nil {
+		t.Fatalf("failed to create client: %s", err.Error())
+	}
+
+	// Create some random trace spans.
+	NUM_TEST_SPANS := 30
+	rnd := rand.New(rand.NewSource(2))
+	allSpans := make([]*common.Span, NUM_TEST_SPANS)
+	allSpans[0] = test.NewRandomSpan(rnd, allSpans[0:0])
+	for i := 1; i < NUM_TEST_SPANS; i++ {
+		allSpans[i] = test.NewRandomSpan(rnd, allSpans[1:i])
+	}
+	allSpans[1].SpanData.Parents = []common.SpanId{common.SpanId(allSpans[0].Id)}
+
+	// Write half of the spans to htraced via the client.
+	for i := 0; i < NUM_TEST_SPANS/2; i++ {
+		if err := hcl.WriteSpan(allSpans[i]); err != nil {
+			t.Fatalf("WriteSpan(%d) failed: %s\n", i, err.Error())
+		}
+	}
+
+	// Look up the first half of the spans.  They should be found.
+	var span *common.Span
+	for i := 0; i < NUM_TEST_SPANS/2; i++ {
+		span, err = hcl.FindSpan(allSpans[i].Id)
+		if err != nil {
+			t.Fatalf("FindSpan(%d) failed: %s\n", i, err.Error())
+		}
+		common.ExpectSpansEqual(t, allSpans[i], span)
+	}
+
+	// Look up the second half of the spans.  They should not be found.
+	for i := NUM_TEST_SPANS / 2; i < NUM_TEST_SPANS; i++ {
+		span, err = hcl.FindSpan(allSpans[i].Id)
+		if err != nil {
+			t.Fatalf("FindSpan(%d) failed: %s\n", i, err.Error())
+		}
+		if span != nil {
+			t.Fatalf("Unexpectedly found a span we never write to "+
+				"the server: FindSpan(%d) succeeded\n", i)
+		}
+	}
+
+	// Test FindChildren
+	childSpan := allSpans[1]
+	parentId := childSpan.Parents[0]
+	var children []common.SpanId
+	children, err = hcl.FindChildren(parentId, 1)
+	if err != nil {
+		t.Fatalf("FindChildren(%s) failed: %s\n", parentId, err.Error())
+	}
+	if len(children) != 1 {
+		t.Fatalf("FindChildren(%s) returned an invalid number of "+
+			"children: expected %d, got %d\n", parentId, 1, len(children))
+	}
+	if children[0] != childSpan.Id {
+		t.Fatalf("FindChildren(%s) returned an invalid child id: expected %s, "+
+			" got %s\n", parentId, childSpan.Id, children[0])
+	}
+
+	// Test FindChildren on a span that has no children
+	childlessSpan := allSpans[NUM_TEST_SPANS/2]
+	children, err = hcl.FindChildren(childlessSpan.Id, 10)
+	if err != nil {
+		t.Fatalf("FindChildren(%d) failed: %s\n", childlessSpan.Id, err.Error())
+	}
+	if len(children) != 0 {
+		t.Fatalf("FindChildren(%d) returned an invalid number of "+
+			"children: expected %d, got %d\n", childlessSpan.Id, 0, len(children))
+	}
+}
diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go
index b43d4ce..2137063 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore.go
@@ -210,7 +210,7 @@
 	return nil
 }
 
-func (shd *shard) FindChildren(sid int64, childIds []int64, lim int32) ([]int64, int32, error) {
+func (shd *shard) FindChildren(sid int64, childIds []common.SpanId, lim int32) ([]common.SpanId, int32, error) {
 	searchKey := makeKey('p', sid)
 	iter := shd.ldb.NewIterator(shd.store.readOpts)
 	defer iter.Close()
@@ -226,7 +226,7 @@
 		if !bytes.HasPrefix(key, searchKey) {
 			break
 		}
-		id := keyToInt(key[9:])
+		id := common.SpanId(keyToInt(key[9:]))
 		childIds = append(childIds, id)
 		lim--
 		iter.Next()
@@ -390,7 +390,7 @@
 		if strings.Index(err.Error(), "NotFound:") != -1 {
 			return nil
 		}
-		log.Printf("Shard(%s): FindSpan(%d) error: %s\n",
+		log.Printf("Shard(%s): FindSpan(%016x) error: %s\n",
 			shd.path, sid, err.Error())
 		return nil
 	}
@@ -399,7 +399,7 @@
 	data := common.SpanData{}
 	err = decoder.Decode(&data)
 	if err != nil {
-		log.Printf("Shard(%s): FindSpan(%d) decode error: %s\n",
+		log.Printf("Shard(%s): FindSpan(%016x) decode error: %s\n",
 			shd.path, sid, err.Error())
 		return nil
 	}
@@ -412,8 +412,8 @@
 }
 
 // Find the children of a given span id.
-func (store *dataStore) FindChildren(sid int64, lim int32) []int64 {
-	childIds := make([]int64, 0)
+func (store *dataStore) FindChildren(sid int64, lim int32) []common.SpanId {
+	childIds := make([]common.SpanId, 0)
 	var err error
 
 	startIdx := store.getShardIndex(sid)
@@ -426,7 +426,7 @@
 		shd := store.shards[idx]
 		childIds, lim, err = shd.FindChildren(sid, childIds, lim)
 		if err != nil {
-			log.Printf("Shard(%s): FindChildren(%d) error: %s\n",
+			log.Printf("Shard(%s): FindChildren(%016x) error: %s\n",
 				shd.path, sid, err.Error())
 		}
 		idx++
diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go
index f037145..f0449fe 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/datastore_test.go
@@ -107,7 +107,7 @@
 	if len(children) != 2 {
 		t.Fatalf("expected 2 children, but got %d\n", len(children))
 	}
-	sort.Sort(common.Int64Slice(children))
+	sort.Sort(common.SpanIdSlice(children))
 	if children[0] != 2 {
 		t.Fatal()
 	}
diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go b/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go
index 403b1f9..d444a02 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/htraced.go
@@ -22,16 +22,23 @@
 import (
 	"log"
 	"org/apache/htrace/conf"
+	"time"
 )
 
 var RELEASE_VERSION string
 var GIT_VERSION string
 
 func main() {
-	cnf := conf.LoadApplicationConfig()
+	cnf := conf.LoadApplicationConfig(nil)
 	store, err := CreateDataStore(cnf, nil)
 	if err != nil {
 		log.Fatalf("Error creating datastore: %s\n", err.Error())
 	}
-	startRestServer(cnf, store)
+	_, err = CreateRestServer(cnf, store)
+	if err != nil {
+		log.Fatalf("Error creating REST server: %s\n", err.Error())
+	}
+	for {
+		time.Sleep(time.Duration(10) * time.Hour)
+	}
 }
diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go b/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go
index 43d7e23..fd8c2a7 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/mini_htraced.go
@@ -59,11 +59,13 @@
 	Cnf      *conf.Config
 	DataDirs []string
 	Store    *dataStore
+	Rsv      *RestServer
 }
 
 func (bld *MiniHTracedBuilder) Build() (*MiniHTraced, error) {
 	var err error
 	var store *dataStore
+	var rsv *RestServer
 	if bld.Name == "" {
 		bld.Name = "HTraceTest"
 	}
@@ -84,6 +86,9 @@
 					os.RemoveAll(dataDirs[idx])
 				}
 			}
+			if rsv != nil {
+				rsv.Close()
+			}
 		}
 	}()
 	for idx := range dataDirs {
@@ -94,6 +99,7 @@
 		}
 	}
 	bld.Cnf[conf.HTRACE_DATA_STORE_DIRECTORIES] = strings.Join(dataDirs, conf.PATH_LIST_SEP)
+	bld.Cnf[conf.HTRACE_WEB_ADDRESS] = ":0" // use a random port for the REST server
 	cnfBld := conf.Builder{Values: bld.Cnf, Defaults: conf.DEFAULTS}
 	cnf, err := cnfBld.Build()
 	if err != nil {
@@ -103,14 +109,25 @@
 	if err != nil {
 		return nil, err
 	}
+	rsv, err = CreateRestServer(cnf, store)
+	if err != nil {
+		return nil, err
+	}
 	return &MiniHTraced{
 		Cnf:      cnf,
 		DataDirs: dataDirs,
 		Store:    store,
+		Rsv:      rsv,
 	}, nil
 }
 
+// Return a Config object that clients can use to connect to this MiniHTraceD.
+func (ht *MiniHTraced) ClientConf() *conf.Config {
+	return ht.Cnf.Clone(conf.HTRACE_WEB_ADDRESS, ht.Rsv.Addr().String())
+}
+
 func (ht *MiniHTraced) Close() {
+	ht.Rsv.Close()
 	ht.Store.Close()
 	for idx := range ht.DataDirs {
 		os.RemoveAll(ht.DataDirs[idx])
diff --git a/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go b/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
index 6db1b7d..d175f4e 100644
--- a/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
+++ b/htrace-core/src/go/src/org/apache/htrace/htraced/rest.go
@@ -26,6 +26,7 @@
 	"io"
 	"log"
 	"mime"
+	"net"
 	"net/http"
 	"org/apache/htrace/common"
 	"org/apache/htrace/conf"
@@ -41,9 +42,8 @@
 }
 
 // Write a JSON error response.
-func writeError(w http.ResponseWriter, errCode int, fstr string, args ...interface{}) {
-	str := fmt.Sprintf(fstr, args)
-	str = strings.Replace(str, `"`, `'`, -1)
+func writeError(w http.ResponseWriter, errCode int, errStr string) {
+	str := strings.Replace(errStr, `"`, `'`, -1)
 	log.Println(str)
 	w.WriteHeader(errCode)
 	w.Write([]byte(`{ "error" : "` + str + `"}`))
@@ -59,7 +59,7 @@
 	buf, err := json.Marshal(&version)
 	if err != nil {
 		writeError(w, http.StatusInternalServerError,
-			"error marshalling ServerInfo: %s\n", err.Error())
+			fmt.Sprintf("error marshalling ServerInfo: %s\n", err.Error()))
 		return
 	}
 	w.Write(buf)
@@ -72,8 +72,8 @@
 func (hand *dataStoreHandler) parse64(w http.ResponseWriter, str string) (int64, bool) {
 	val, err := strconv.ParseUint(str, 16, 64)
 	if err != nil {
-		writeError(w, http.StatusBadRequest, "Failed to parse span ID %s: %s",
-			str, err.Error())
+		writeError(w, http.StatusBadRequest,
+			fmt.Sprintf("Failed to parse span ID %s: %s", str, err.Error()))
 		w.Write([]byte("Error parsing : " + err.Error()))
 		return -1, false
 	}
@@ -84,12 +84,13 @@
 	req *http.Request) (int32, bool) {
 	str := req.FormValue(fieldName)
 	if str == "" {
-		writeError(w, http.StatusBadRequest, "No %s specified.", fieldName)
+		writeError(w, http.StatusBadRequest, fmt.Sprintf("No %s specified.", fieldName))
 		return -1, false
 	}
 	val, err := strconv.ParseUint(str, 16, 32)
 	if err != nil {
-		writeError(w, http.StatusBadRequest, "Error parsing %s: %s.", fieldName, err.Error())
+		writeError(w, http.StatusBadRequest,
+			fmt.Sprintf("Error parsing %s: %s.", fieldName, err.Error()))
 		return -1, false
 	}
 	return int32(val), true
@@ -110,7 +111,8 @@
 	}
 	span := hand.store.FindSpan(sid)
 	if span == nil {
-		writeError(w, http.StatusNoContent, "No spans were specified.")
+		writeError(w, http.StatusNoContent, fmt.Sprintf("No such span as %s",
+			common.SpanId(sid)))
 		return
 	}
 	w.Write(span.ToJson())
@@ -155,7 +157,8 @@
 		err := dec.Decode(&span)
 		if err != nil {
 			if err != io.EOF {
-				writeError(w, http.StatusBadRequest, "Error parsing spans: %s", err.Error())
+				writeError(w, http.StatusBadRequest,
+					fmt.Sprintf("Error parsing spans: %s", err.Error()))
 				return
 			}
 			break
@@ -189,7 +192,17 @@
 	w.Write([]byte(rsc))
 }
 
-func startRestServer(cnf *conf.Config, store *dataStore) {
+type RestServer struct {
+	listener net.Listener
+}
+
+func CreateRestServer(cnf *conf.Config, store *dataStore) (*RestServer, error) {
+	var err error
+	rsv := &RestServer{}
+	rsv.listener, err = net.Listen("tcp", cnf.Get(conf.HTRACE_WEB_ADDRESS))
+	if err != nil {
+		return nil, err
+	}
 
 	r := mux.NewRouter().StrictSlash(false)
 	// Default Handler. This will serve requests for static requests.
@@ -207,6 +220,16 @@
 	findChildrenH := &findChildrenHandler{dataStoreHandler: dataStoreHandler{store: store}}
 	span.Handle("/{id}/children", findChildrenH).Methods("GET")
 
-	http.ListenAndServe(cnf.Get(conf.HTRACE_WEB_ADDRESS), r)
+	go http.Serve(rsv.listener, r)
+
 	log.Println("Started REST server...")
+	return rsv, nil
+}
+
+func (rsv *RestServer) Addr() net.Addr {
+	return rsv.listener.Addr()
+}
+
+func (rsv *RestServer) Close() {
+	rsv.listener.Close()
 }