// It returns an error that satisfies IsConnectionError
// when the connection cannot be made.
?
I'm not quite sure why we want to distinguish a connection
error from the other kinds of errors though.
https://codereview.appspot.com/85850043/diff/20001/state/api/client.go#newcode759
state/api/client.go:759: // end point (not sure why). So do a version
check, as version was added
The reason that the websocket connection hangs is that
the server is serving the API on every URL (that was
a mistake that I should have fixed earlier, sorry),
so you're waiting for an RPC reply without sending
any request.
data := make([]byte, maxInitialLineSize)
n, err := connection.Read(data)
if err != nil {
return nil, fmt.Errorf("unable to read initial response: %v", err)
}
data = data[0:n]
because websockets guarantees that it will read at
most one frame. Alternatively, you could just use
bufio as you tried originally.
The problem you were having with bufio reading too much
data was a problem with your test (your echoUrl function
was returning a bytes.Buffer, not a reader that preserved
message boundaries).
Looks great in general. Quite a few minor points but nothing too
substantial.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go
File state/api/client.go (right):
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode715 client. go:715: func (c *Client) Version() (version.Number,
state/api/
error) {
I think I'd call this AgentVersion.
We might have different ideas of API version later.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode720 client. go:720: if result.Error != nil {
state/api/
This isn't possible. The Error field is only there for bulk version
results.
I'd either ignore the field, or make a new type in params
(e.g. type AgentVersionResult {Version version.Number})
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode726 client. go:726: // Allow overriding in tests. nfig, because that's what
state/api/
I'd be tempted to name this websocketDialCo
we're
mocking.
// websocketDialConfig is called instead of websocket. DialConfig
// so we can override it in tests.
?
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode735 client. go:735: // IsConnectionError returns true if the error
state/api/
is a connection error.
// IsConnectionError reports whether the error is a connection
// error.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode741 client. go:741: // Params for WatchDebugLog
state/api/
Please can we document this type and its fields?
This is the public face of the Go Juju API.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode755 client. go:755: // machines or units. The watching is started
state/api/
the given number of
The last sentence doesn't seem to fit here.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode756 client. go:756: // matching lines back in history.
state/api/
// It returns an error that satisfies IsConnectionError
// when the connection cannot be made.
?
I'm not quite sure why we want to distinguish a connection
error from the other kinds of errors though.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode759 client. go:759: // end point (not sure why). So do a version
state/api/
check, as version was added
The reason that the websocket connection hangs is that
the server is serving the API on every URL (that was
a mistake that I should have fixed earlier, sorry),
so you're waiting for an RPC reply without sending
any request.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode768 client. go:768: attrs["replay"] = strconv. FormatBool( args.Replay) } fmt.Sprint( args.Replay) )
state/api/
[]string{
attrs.Set(
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode771 client. go:771: attrs["maxLines"] = fmt.Sprint( args.Limit) } fmt.Sprint( args.Limit) )
state/api/
[]string{
attrs.Set(
etc
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode780 client. go:780: if len(arg) > 0 {
state/api/
Is this actually necessary. If there's a zero-length slice in the
attributes, does it matter if it's nil or empty?
I think just includeEntity" ] = args.IncludeEntity
attrs["
etc
should probably work fine.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode804 client. go:804: // reads too much from the reader.
state/api/
Actually, you can just do:
data := make([]byte, maxInitialLineSize) Read(data)
n, err := connection.
if err != nil {
return nil, fmt.Errorf("unable to read initial response: %v", err)
}
data = data[0:n]
because websockets guarantees that it will read at
most one frame. Alternatively, you could just use
bufio as you tried originally.
The problem you were having with bufio reading too much
data was a problem with your test (your echoUrl function
was returning a bytes.Buffer, not a reader that preserved
message boundaries).
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode820 client. go:820: logger. Debugf( "initial line: %v", string(line))
state/api/
you can use %s and lose the string conversion here.
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client. go#newcode827 client. go:827: return nil, fmt.Errorf( errResult. Error.Message)
state/api/
return nil, errResult.Error
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client_ test.go client_ test.go (right):
File state/api/
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client_ test.go# newcode126 client_ test.go: 126: junk := bytes.NewBuffer String( "junk\n" )
state/api/
or strings.NewReader
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client_ test.go# newcode137 client_ test.go: 137: junk := bytes.NewBuffer String( "junk")
state/api/
ditto
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client_ test.go# newcode173 client_ test.go: 173: c.Assert( values[ "includeEntity" ], IncludeEntity) tity": params. IncludeEntity, dule": params. IncludeModule,
state/api/
jc.SameContents, params.
or
c.Assert(values, jc.DeepEquals, url.Values{
"includeEn
"includeMo
"maxLines": {"100"},
"backlog": {"200"}.
etc
}
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client_ test.go# newcode185 client_ test.go: 185: type closableBuffer struct {
state/api/
You can use ioutil.NopCloser instead of this (if
you want the logging, you can just implement
a reader that does that only)
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client_ test.go# newcode201 client_ test.go: 201: // badReader raises err when read is
state/api/
attempted
// badReader returns err when Read is called.
?
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/api/ client_ test.go# newcode211 client_ test.go: 211: func echoUrl(c *gc.C) .Config) (io.ReadCloser, error) {
state/api/
func(*websocket
Here's the source of your buffering problem.
This fixes it:
func echoUrl(c *gc.C) func(*websocket .Config) (io.ReadCloser, error) { ¶ms. ErrorResult{ })
message, err := json.Marshal(
c.Assert(err, gc.IsNil)
return func(config *websocket.Config) (io.ReadCloser, error) {
pr, pw := io.Pipe()
go func() {
fmt.Fprintf(pw, "%s\n", message)
fmt.Fprintf(pw, "%s\n", config.Location)
}()
return pr, nil
}
}
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/apiserver /client/ client. go /client/ client. go (right):
File state/apiserver
https:/ /codereview. appspot. com/85850043/ diff/20001/ state/apiserver /client/ client. go#newcode788 /client/ client. go:788: return VersionResult{ Version: ¤t}, nil
state/apiserver
params.
or just:
return params. VersionResult{ Version: &version. Current. Number}
https:/ /codereview. appspot. com/85850043/ diff/20001/ utils/http. go
File utils/http.go (right):
https:/ /codereview. appspot. com/85850043/ diff/20001/ utils/http. go#newcode96 Header( username, password string)
utils/http.go:96: func CreateBasicAuth
http.Header {
just "BasicAuthHeader" might work well as a name here.
if you don't like that, then NewBasicAuthHeader would
probably be more idiomatic than Create.
https:/ /codereview. appspot. com/85850043/ diff/20001/ utils/http_ test.go
File utils/http_test.go (right):
https:/ /codereview. appspot. com/85850043/ diff/20001/ utils/http_ test.go# newcode95 test.go: 95: fields = strings. Split(string( decoded) , ":") string( decoded) , gc.Equals, "eric:sekrit")
utils/http_
or c.Assert(
https:/ /codereview. appspot. com/85850043/