Skip to content

Commit 673864c

Browse files
authored
Merge pull request #657 from ably/feature/realtime-fallbacks
[ECO-4327] Feature - realtime fallbacks
2 parents f749c38 + 63cb110 commit 673864c

11 files changed

+389
-76
lines changed

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,6 @@ See [jwt auth issue](https://github.com/ably/ably-go/issues/569) for more detail
450450
- Inband reauthentication is not supported; expiring tokens will trigger a disconnection and resume of a realtime
451451
connection. See [server initiated auth](https://github.com/ably/ably-go/issues/228) for more details.
452452

453-
- Realtime connection failure handling is partially implemented. See [host fallback](https://github.com/ably/ably-go/issues/225) for more details.
454-
455453
- Channel suspended state is partially implemented. See [suspended channel state](https://github.com/ably/ably-go/issues/568).
456454

457455
- Realtime Ping function is not implemented.

ably/error.go

+11
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,17 @@ func errFromUnprocessableBody(resp *http.Response) error {
147147
return &ErrorInfo{Code: ErrBadRequest, StatusCode: resp.StatusCode, err: err}
148148
}
149149

150+
func isTimeoutOrDnsErr(err error) bool {
151+
var netErr net.Error
152+
if errors.As(err, &netErr) {
153+
if netErr.Timeout() { // RSC15l2
154+
return true
155+
}
156+
}
157+
var dnsErr *net.DNSError
158+
return errors.As(err, &dnsErr) // RSC15l1
159+
}
160+
150161
func checkValidHTTPResponse(resp *http.Response) error {
151162
type errorBody struct {
152163
Error errorInfo `json:"error,omitempty" codec:"error,omitempty"`

ably/export_test.go

+17-4
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ func (opts *clientOptions) RestURL() string {
3636
return opts.restURL()
3737
}
3838

39-
func (opts *clientOptions) RealtimeURL() string {
40-
return opts.realtimeURL()
41-
}
42-
4339
func (c *REST) Post(ctx context.Context, path string, in, out interface{}) (*http.Response, error) {
4440
return c.post(ctx, path, in, out)
4541
}
@@ -93,6 +89,10 @@ func (c *REST) GetCachedFallbackHost() string {
9389
return c.hostCache.get()
9490
}
9591

92+
func (c *REST) ActiveRealtimeHost() string {
93+
return c.activeRealtimeHost
94+
}
95+
9696
func (c *RealtimeChannel) GetChannelSerial() string {
9797
c.mtx.Lock()
9898
defer c.mtx.Unlock()
@@ -121,6 +121,10 @@ func (opts *clientOptions) GetFallbackRetryTimeout() time.Duration {
121121
return opts.fallbackRetryTimeout()
122122
}
123123

124+
func (opts *clientOptions) HasActiveInternetConnection() bool {
125+
return opts.hasActiveInternetConnection()
126+
}
127+
124128
func NewErrorInfo(code ErrorCode, err error) *ErrorInfo {
125129
return newError(code, err)
126130
}
@@ -222,6 +226,10 @@ func (c *Connection) SetKey(key string) {
222226
c.key = key
223227
}
224228

229+
func (r *Realtime) Rest() *REST {
230+
return r.rest
231+
}
232+
225233
func (c *RealtimePresence) Members() map[string]*PresenceMessage {
226234
c.mtx.Lock()
227235
defer c.mtx.Unlock()
@@ -272,6 +280,11 @@ type DurationFromMsecs = durationFromMsecs
272280
type ProtoErrorInfo = errorInfo
273281
type ProtoFlag = protoFlag
274282
type ProtocolMessage = protocolMessage
283+
type WebsocketErr = websocketErr
284+
285+
func (w *WebsocketErr) HttpResp() *http.Response {
286+
return w.resp
287+
}
275288

276289
const (
277290
DefaultCipherKeyLength = defaultCipherKeyLength

ably/options.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package ably
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"fmt"
8+
"io"
79
"log"
810
"net"
911
"net/http"
@@ -28,6 +30,10 @@ const (
2830
Port = 80
2931
TLSPort = 443
3032
maxMessageSize = 65536 // 64kb, default value TO3l8
33+
34+
// RTN17c
35+
internetCheckUrl = "https://internet-up.ably-realtime.com/is-the-internet-up.txt"
36+
internetCheckOk = "yes"
3137
)
3238

3339
var defaultOptions = clientOptions{
@@ -482,10 +488,10 @@ func (opts *clientOptions) restURL() (restUrl string) {
482488
return "https://" + baseUrl
483489
}
484490

485-
func (opts *clientOptions) realtimeURL() (realtimeUrl string) {
486-
baseUrl := opts.getRealtimeHost()
491+
func (opts *clientOptions) realtimeURL(realtimeHost string) (realtimeUrl string) {
492+
baseUrl := realtimeHost
487493
_, _, err := net.SplitHostPort(baseUrl)
488-
if err != nil { // set port if not set in baseUrl
494+
if err != nil { // set port if not set in provided realtimeHost
489495
port, _ := opts.activePort()
490496
baseUrl = net.JoinHostPort(baseUrl, strconv.Itoa(port))
491497
}
@@ -595,6 +601,20 @@ func (opts *clientOptions) idempotentRESTPublishing() bool {
595601
return opts.IdempotentRESTPublishing
596602
}
597603

604+
// RTN17c
605+
func (opts *clientOptions) hasActiveInternetConnection() bool {
606+
res, err := opts.httpclient().Get(internetCheckUrl)
607+
if err != nil || res.StatusCode != 200 {
608+
return false
609+
}
610+
defer res.Body.Close()
611+
data, err := io.ReadAll(res.Body)
612+
if err != nil {
613+
return false
614+
}
615+
return bytes.Contains(data, []byte(internetCheckOk))
616+
}
617+
598618
type ScopeParams struct {
599619
Start time.Time
600620
End time.Time

ably/options_test.go

+23-22
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,32 @@ import (
1414
)
1515

1616
func TestDefaultFallbacks_RSC15h(t *testing.T) {
17-
t.Run("with env should return environment fallback hosts", func(t *testing.T) {
18-
expectedFallBackHosts := []string{
19-
"a.ably-realtime.com",
20-
"b.ably-realtime.com",
21-
"c.ably-realtime.com",
22-
"d.ably-realtime.com",
23-
"e.ably-realtime.com",
24-
}
25-
hosts := ably.DefaultFallbackHosts()
26-
assert.Equal(t, expectedFallBackHosts, hosts)
27-
})
17+
expectedFallBackHosts := []string{
18+
"a.ably-realtime.com",
19+
"b.ably-realtime.com",
20+
"c.ably-realtime.com",
21+
"d.ably-realtime.com",
22+
"e.ably-realtime.com",
23+
}
24+
hosts := ably.DefaultFallbackHosts()
25+
assert.Equal(t, expectedFallBackHosts, hosts)
2826
}
2927

3028
func TestEnvFallbackHosts_RSC15i(t *testing.T) {
31-
t.Run("with env should return environment fallback hosts", func(t *testing.T) {
32-
expectedFallBackHosts := []string{
33-
"sandbox-a-fallback.ably-realtime.com",
34-
"sandbox-b-fallback.ably-realtime.com",
35-
"sandbox-c-fallback.ably-realtime.com",
36-
"sandbox-d-fallback.ably-realtime.com",
37-
"sandbox-e-fallback.ably-realtime.com",
38-
}
39-
hosts := ably.GetEnvFallbackHosts("sandbox")
40-
assert.Equal(t, expectedFallBackHosts, hosts)
41-
})
29+
expectedFallBackHosts := []string{
30+
"sandbox-a-fallback.ably-realtime.com",
31+
"sandbox-b-fallback.ably-realtime.com",
32+
"sandbox-c-fallback.ably-realtime.com",
33+
"sandbox-d-fallback.ably-realtime.com",
34+
"sandbox-e-fallback.ably-realtime.com",
35+
}
36+
hosts := ably.GetEnvFallbackHosts("sandbox")
37+
assert.Equal(t, expectedFallBackHosts, hosts)
38+
}
39+
40+
func TestInternetConnectionCheck_RTN17c(t *testing.T) {
41+
clientOptions := ably.NewClientOptions()
42+
assert.True(t, clientOptions.HasActiveInternetConnection())
4243
}
4344

4445
func TestFallbackHosts_RSC15b(t *testing.T) {

0 commit comments

Comments
 (0)