mirror of https://github.com/docker/docs.git
Merge pull request #49 from smarterclayton/add_websocket_server
This commit is contained in:
commit
f67c1b7e4b
|
@ -0,0 +1,66 @@
|
|||
package http2
|
||||
|
||||
import (
|
||||
"github.com/docker/libswarm/beam"
|
||||
"github.com/docker/spdystream"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Serve a Beam endpoint over a single HTTP2 connection
|
||||
type Server struct {
|
||||
conn *spdystream.Connection
|
||||
streamChan chan *spdystream.Stream
|
||||
streamLock sync.RWMutex
|
||||
subStreamChans map[string]chan *spdystream.Stream
|
||||
}
|
||||
|
||||
// Create a Beam receiver from a net.Conn
|
||||
func NewServer(conn net.Conn) (*Server, error) {
|
||||
spdyConn, err := spdystream.NewConnection(conn, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
conn: spdyConn,
|
||||
streamChan: make(chan *spdystream.Stream),
|
||||
subStreamChans: make(map[string]chan *spdystream.Stream),
|
||||
}
|
||||
go s.conn.Serve(s.streamHandler, spdystream.NoAuthHandler)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
||||
|
||||
func (s *Server) Receive(mode int) (*beam.Message, error) {
|
||||
stream := <-s.streamChan
|
||||
return createStreamMessage(stream, mode, s, nil)
|
||||
}
|
||||
|
||||
func (s *Server) streamHandler(stream *spdystream.Stream) {
|
||||
streamChan := s.getStreamChan(stream.Parent())
|
||||
streamChan <- stream
|
||||
}
|
||||
|
||||
func (s *Server) addStreamChan(stream *spdystream.Stream, streamChan chan *spdystream.Stream) {
|
||||
s.streamLock.Lock()
|
||||
s.subStreamChans[stream.String()] = streamChan
|
||||
s.streamLock.Unlock()
|
||||
}
|
||||
|
||||
func (s *Server) getStreamChan(stream *spdystream.Stream) chan *spdystream.Stream {
|
||||
if stream == nil {
|
||||
return s.streamChan
|
||||
}
|
||||
s.streamLock.RLock()
|
||||
defer s.streamLock.RUnlock()
|
||||
streamChan, ok := s.subStreamChans[stream.String()]
|
||||
if ok {
|
||||
return streamChan
|
||||
}
|
||||
return s.streamChan
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package ws
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/docker/libswarm/beam"
|
||||
"github.com/docker/libswarm/beam/http2"
|
||||
"github.com/docker/spdystream/ws"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Connect to a Beam server over a Websocket connection as a client
|
||||
func NewSender(wsConn *websocket.Conn) (beam.Sender, error) {
|
||||
session, err := http2.NewStreamSession(ws.NewConnection(wsConn))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Upgrade an HTTP connection to a Beam over HTTP2 over
|
||||
// Websockets connection.
|
||||
type Upgrader struct {
|
||||
Upgrader websocket.Upgrader
|
||||
}
|
||||
|
||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*http2.Server, error) {
|
||||
wsConn, err := u.Upgrader.Upgrade(w, r, responseHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
netConn := ws.NewConnection(wsConn)
|
||||
server, err := http2.NewServer(netConn)
|
||||
if err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// Returns true if a handshake error occured in websockets, which means
|
||||
// a response has already been written to the stream.
|
||||
func IsHandshakeError(err error) bool {
|
||||
_, ok := err.(websocket.HandshakeError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type BeamFunc func(beam.Receiver)
|
||||
|
||||
// Handler function for serving Beam over HTTP. Will invoke f and
|
||||
// then close the server's Beam endpoint after f returns.
|
||||
func Serve(u *Upgrader, f BeamFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
u.Upgrader.Error(w, r, http.StatusMethodNotAllowed, errors.New("Method not allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
server, err := u.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
if !IsHandshakeError(err) {
|
||||
u.Upgrader.Error(w, r, http.StatusInternalServerError, errors.New("Unable to open an HTTP2 connection over Websockets"))
|
||||
}
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
f(server)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package ws
|
||||
|
||||
import (
|
||||
"github.com/docker/libswarm/beam"
|
||||
"github.com/gorilla/websocket"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServe(t *testing.T) {
|
||||
gotAck := make(chan bool)
|
||||
u := &Upgrader{}
|
||||
server := httptest.NewServer(Serve(u, func(r beam.Receiver) {
|
||||
msg, msgErr := r.Receive(beam.Ret)
|
||||
if msgErr != nil {
|
||||
t.Fatalf("Error receiving message: %s", msgErr)
|
||||
}
|
||||
if msg.Att == nil {
|
||||
t.Fatalf("Error message missing attachment")
|
||||
}
|
||||
if msg.Verb != beam.Attach {
|
||||
t.Fatalf("Wrong verb\nActual: %s\nExpecting: %s", msg.Verb, beam.Attach)
|
||||
}
|
||||
|
||||
receiver, sendErr := msg.Ret.Send(&beam.Message{Verb: beam.Ack})
|
||||
if sendErr != nil {
|
||||
t.Fatalf("Error sending return message: %s", sendErr)
|
||||
}
|
||||
|
||||
_, ackErr := receiver.Receive(0)
|
||||
if ackErr == nil {
|
||||
t.Fatalf("No error receiving from message with no return pipe")
|
||||
}
|
||||
if ackErr != io.EOF {
|
||||
t.Fatalf("Unexpected error receiving from message: %s", ackErr)
|
||||
}
|
||||
|
||||
<-gotAck
|
||||
}))
|
||||
|
||||
wsConn, _, err := websocket.DefaultDialer.Dial(strings.Replace(server.URL, "http://", "ws://", 1), http.Header{"Origin": {server.URL}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sender, senderErr := NewSender(wsConn)
|
||||
if senderErr != nil {
|
||||
t.Fatalf("Error creating sender: %s", senderErr)
|
||||
}
|
||||
|
||||
receiver, sendErr := sender.Send(&beam.Message{Verb: beam.Attach, Ret: beam.RetPipe})
|
||||
if sendErr != nil {
|
||||
t.Fatalf("Error sending message: %s", sendErr)
|
||||
}
|
||||
|
||||
msg, receiveErr := receiver.Receive(beam.Ret)
|
||||
if receiveErr != nil {
|
||||
t.Fatalf("Error receiving message")
|
||||
}
|
||||
|
||||
if msg.Verb != beam.Ack {
|
||||
t.Fatalf("Wrong verb\nActual: %s\nExpecting: %s", msg.Verb, beam.Ack)
|
||||
}
|
||||
|
||||
gotAck <- true
|
||||
|
||||
shutdownErr := sender.Close()
|
||||
if shutdownErr != nil && !strings.Contains(shutdownErr.Error(), "broken pipe") {
|
||||
t.Fatalf("Error closing: %s", shutdownErr)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue