@ -22,7 +22,9 @@ import (
"encoding/xml"
"encoding/xml"
"fmt"
"fmt"
"io"
"io"
"net"
"net/http"
"net/http"
"syscall"
"time"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/mux"
@ -213,14 +215,6 @@ func writeNotification(w http.ResponseWriter, notification map[string][]Notifica
return err
return err
}
}
// https://github.com/containous/traefik/issues/560
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
//
// Proxies might buffer the connection to avoid this we
// need the proper MIME type before writing to client.
// This MIME header tells the proxies to avoid buffering
w . Header ( ) . Set ( "Content-Type" , "text/event-stream" )
// Add additional CRLF characters for client to
// Add additional CRLF characters for client to
// differentiate the individual events properly.
// differentiate the individual events properly.
_ , err = w . Write ( append ( notificationBytes , crlf ... ) )
_ , err = w . Write ( append ( notificationBytes , crlf ... ) )
@ -232,25 +226,61 @@ func writeNotification(w http.ResponseWriter, notification map[string][]Notifica
// CRLF character used for chunked transfer in accordance with HTTP standards.
// CRLF character used for chunked transfer in accordance with HTTP standards.
var crlf = [ ] byte ( "\r\n" )
var crlf = [ ] byte ( "\r\n" )
// sendBucketNotification - writes notification back to client on the response writer
// listenChan A `listenChan` provides a data channel to send event
// for each notification input, otherwise writes whitespace characters periodically
// notifications on and `doneCh` to signal that events are no longer
// to keep the connection active. Each notification messages are terminated by CRLF
// being received. It also sends empty events (whitespace) to keep the
// character. Upon any error received on response writer the for loop exits.
// underlying connection alive.
func sendBucketNotification ( w http . ResponseWriter , arnListenerCh <- chan [ ] NotificationEvent ) {
type listenChan struct {
var dummyEvents = map [ string ] [ ] NotificationEvent { "Records" : nil }
doneCh chan struct { }
// Continuously write to client either timely empty structures
dataCh chan [ ] NotificationEvent
// every 5 seconds, or return back the notifications.
}
// newListenChan returns a listenChan with properly initialized
// unbuffered channels.
func newListenChan ( ) * listenChan {
return & listenChan {
doneCh : make ( chan struct { } ) ,
dataCh : make ( chan [ ] NotificationEvent ) ,
}
}
// sendNotificationEvent sends notification events on the data channel
// unless doneCh is not closed
func ( l * listenChan ) sendNotificationEvent ( events [ ] NotificationEvent ) {
select {
// Returns immediately if receiver has quit.
case <- l . doneCh :
// Blocks until receiver is available.
case l . dataCh <- events :
}
}
// waitForListener writes event notification OR whitespaces on
// ResponseWriter until client closes connection
func ( l * listenChan ) waitForListener ( w http . ResponseWriter ) {
// Logs errors other than EPIPE and ECONNRESET.
// EPIPE and ECONNRESET indicate that the client stopped
// listening to notification events.
logClientError := func ( err error , msg string ) {
if oe , ok := err . ( * net . OpError ) ; ok && ( oe . Err == syscall . EPIPE || oe . Err ==
syscall . ECONNRESET ) {
errorIf ( err , msg )
}
}
emptyEvent := map [ string ] [ ] NotificationEvent { "Records" : nil }
defer close ( l . doneCh )
for {
for {
select {
select {
case events := <- arnListenerCh :
case events := <- l . data Ch:
if err := writeNotification ( w , map [ string ] [ ] NotificationEvent { "Records" : events } ) ; err != nil {
if err := writeNotification ( w , map [ string ] [ ] NotificationEvent { "Records" : events } ) ; err != nil {
errorIf ( err , "Unable to write notification to client." )
logCli entE rror( err , "Unable to write notification" )
return
return
}
}
case <- time . After ( globalSNSConnAlive ) : // Wait for global conn active seconds.
case <- time . After ( globalSNSConnAlive ) :
if err := writeNotification ( w , dummyEvents ) ; err != nil {
if err := writeNotification ( w , emptyEvent ) ; err != nil {
// FIXME - do not log for all errors.
logClientError ( err , "Unable to write empty notification" )
errorIf ( err , "Unable to write notification to client." )
return
return
}
}
}
}
@ -346,12 +376,11 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
} ,
} ,
}
}
// Setup a listening channel that will receive notifications
// Setup a listen channel to receive notifications like
// from the RPC handler.
// s3:ObjectCreated, s3:ObjectDeleted etc.
nEventCh := make ( chan [ ] NotificationEvent )
nListenCh := newListenChan ( )
defer close ( nEventCh )
// Add channel for listener events
// Add channel for listener events
if err = globalEventNotifier . AddListenerChan ( accountARN , nEvent Ch ) ; err != nil {
if err = globalEventNotifier . AddListenerChan ( accountARN , nListen Ch ) ; err != nil {
errorIf ( err , "Error adding a listener!" )
errorIf ( err , "Error adding a listener!" )
writeErrorResponse ( w , toAPIErrorCode ( err ) , r . URL )
writeErrorResponse ( w , toAPIErrorCode ( err ) , r . URL )
return
return
@ -361,8 +390,8 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
defer globalEventNotifier . RemoveListenerChan ( accountARN )
defer globalEventNotifier . RemoveListenerChan ( accountARN )
// Update topic config to bucket config and persist - as soon
// Update topic config to bucket config and persist - as soon
// as this call compe lets, events may start appearing in
// as this call complete s, events may start appearing in
// nEvent Ch
// nListen Ch
lc := listenerConfig {
lc := listenerConfig {
TopicConfig : * topicCfg ,
TopicConfig : * topicCfg ,
TargetServer : targetServer ,
TargetServer : targetServer ,
@ -378,8 +407,16 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
// Add all common headers.
// Add all common headers.
setCommonHeaders ( w )
setCommonHeaders ( w )
// Start sending bucket notifications.
// https://github.com/containous/traefik/issues/560
sendBucketNotification ( w , nEventCh )
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
//
// Proxies might buffer the connection to avoid this we
// need the proper MIME type before writing to client.
// This MIME header tells the proxies to avoid buffering
w . Header ( ) . Set ( "Content-Type" , "text/event-stream" )
// Start writing bucket notifications to ResponseWriter.
nListenCh . waitForListener ( w )
}
}
// AddBucketListenerConfig - Updates on disk state of listeners, and
// AddBucketListenerConfig - Updates on disk state of listeners, and