-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor job controller and websocket logic. #150
Changes from 5 commits
f887d7e
b05259d
0278d6a
0f9b435
60e3d59
1df15b6
a4a8ed5
a0f6bca
a62f03c
a4fb550
b13d4cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package websocket | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/gorilla/websocket" | ||
"github.com/pkg/errors" | ||
"github.com/runatlantis/atlantis/server/logging" | ||
) | ||
|
||
// PartitionKeyGenerator generates partition keys for the multiplexor | ||
type PartitionKeyGenerator interface { | ||
Generate(r *http.Request) (string, error) | ||
} | ||
|
||
// PartitionRegistry is the registry holding each partition | ||
// and is responsible for registering/deregistering new buffers | ||
type PartitionRegistry interface { | ||
Register(key string, buffer chan string) | ||
Deregister(key string, buffer chan string) | ||
} | ||
|
||
// Multiplexor is responsible for handling the data transfer between the storage layer | ||
// and the registry. Note this is still a WIP as right now the registry is assumed to handle | ||
// everything. | ||
type Multiplexor struct { | ||
writer *Writer | ||
keyGenerator PartitionKeyGenerator | ||
registry PartitionRegistry | ||
} | ||
|
||
func NewMultiplexor(log logging.SimpleLogging, keyGenerator PartitionKeyGenerator, registry PartitionRegistry) *Multiplexor { | ||
upgrader := websocket.Upgrader{} | ||
upgrader.CheckOrigin = func(r *http.Request) bool { return true } | ||
return &Multiplexor{ | ||
writer: &Writer{ | ||
upgrader: upgrader, | ||
log: log, | ||
}, | ||
keyGenerator: keyGenerator, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We probably need to assign the registry to the multiplexor? Don't we have a linter that checks for these kind of errors where the passed argument is not used in the method body? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, classic. i don't think we have linters for that. Will fix. |
||
} | ||
} | ||
|
||
// Handle should be called for a given websocket request. It blocks | ||
// while writing to the websocket until the buffer is closed. | ||
func (m *Multiplexor) Handle(w http.ResponseWriter, r *http.Request) error { | ||
key, err := m.keyGenerator.Generate(r) | ||
|
||
if err != nil { | ||
return errors.Wrapf(err, "generating partition key") | ||
} | ||
|
||
// Buffer size set to 1000 to ensure messages get queued. | ||
// TODO: make buffer size configurable | ||
buffer := make(chan string, 1000) | ||
|
||
// spinning up a goroutine for this since we are attempting to block on the read side. | ||
go m.registry.Register(key, buffer) | ||
defer m.registry.Deregister(key, buffer) | ||
|
||
return errors.Wrapf(m.writer.Write(w, r, buffer), "writing to ws %s", key) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package websocket | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/gorilla/websocket" | ||
"github.com/pkg/errors" | ||
"github.com/runatlantis/atlantis/server/logging" | ||
) | ||
|
||
func NewWriter(log logging.SimpleLogging) *Writer { | ||
upgrader := websocket.Upgrader{} | ||
upgrader.CheckOrigin = func(r *http.Request) bool { return true } | ||
return &Writer{ | ||
upgrader: upgrader, | ||
log: log, | ||
} | ||
} | ||
|
||
type Writer struct { | ||
upgrader websocket.Upgrader | ||
|
||
//TODO: Remove dependency on atlantis logger here if we upstream this. | ||
log logging.SimpleLogging | ||
} | ||
|
||
func (w *Writer) Write(rw http.ResponseWriter, r *http.Request, input chan string) error { | ||
conn, err := w.upgrader.Upgrade(rw, r, nil) | ||
|
||
if err != nil { | ||
return errors.Wrap(err, "upgrading websocket connection") | ||
} | ||
|
||
conn.SetCloseHandler(func(code int, text string) error { | ||
// Close the channnel after websocket connection closed. | ||
// Will gracefully exit the ProjectCommandOutputHandler.Receive() call and cleanup. | ||
nishkrishnan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// is it good practice to close at the receiver? Probably not, we should figure out a better | ||
// way to handle this case | ||
close(input) | ||
return nil | ||
}) | ||
|
||
// Add a reader goroutine to listen for socket.close() events. | ||
go w.setReadHandler(conn) | ||
|
||
// block on reading our input channel | ||
for msg := range input { | ||
if err := conn.WriteMessage(websocket.BinaryMessage, []byte("\r"+msg+"\n")); err != nil { | ||
w.log.Warn("Failed to write ws message: %s", err) | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (w *Writer) setReadHandler(c *websocket.Conn) { | ||
for { | ||
_, _, err := c.ReadMessage() | ||
if err != nil { | ||
// CloseGoingAway (1001) when a browser tab is closed. | ||
// Expected behaviour since we have a CloseHandler(), log warning if not a CloseGoingAway | ||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { | ||
w.log.Warn("Failed to read WS message: %s", err) | ||
} | ||
return | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use the
writer.NewWriter()
method instead of instantiating the upgrader here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that method is unused. I'll remove it.