Skip to content
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

Add HCWebSocketSetPingInterval #867

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.xbox.httpclient;

import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;

import okhttp3.Headers;
import okhttp3.OkHttpClient;
Expand All @@ -19,6 +20,11 @@ public final class HttpClientWebSocket extends WebSocketListener {
HttpClientWebSocket(long owner) {
this.headers = new Headers.Builder();
this.owner = owner;
this.pingInterval = 0;
}

public void setPingInterval(long pingInterval) {
this.pingInterval = pingInterval;
}

public void addHeader(String name, String value) {
Expand All @@ -33,10 +39,14 @@ public void connect(String url, String subProtocol) {
.headers(headers.build())
.build();

socket = OK_CLIENT.newWebSocket(request, this);
OkHttpClient clientWithPing = OK_CLIENT.newBuilder()
.pingInterval(pingInterval, TimeUnit.SECONDS) // default is 0, which disables pings
.build();

socket = clientWithPing.newWebSocket(request, this);
}

public boolean sendMessage(String message) {
public boolean sendMessage(String message) {
return socket.send(message);
}

Expand Down Expand Up @@ -96,6 +106,7 @@ protected void finalize()

private final Headers.Builder headers;
private final long owner;
private long pingInterval;

private WebSocket socket;
}
2 changes: 2 additions & 0 deletions Build/libHttpClient.GDK/libHttpClient.GDK.def
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,14 @@ EXPORTS
HCWebSocketGetHeader
HCWebSocketGetHeaderAtIndex
HCWebSocketGetNumHeaders
HCWebSocketGetPingInterval
HCWebSocketGetProxyUri
HCWebSocketSendBinaryMessageAsync
HCWebSocketSendMessageAsync
HCWebSocketSetBinaryMessageFragmentEventFunction
HCWebSocketSetHeader
HCWebSocketSetMaxReceiveBufferSize
HCWebSocketSetPingInterval
HCWebSocketSetProxyUri
HCWinHttpResume
HCWinHttpSuspend
Expand Down
2 changes: 2 additions & 0 deletions Build/libHttpClient.Win32/libHttpClient.Win32.def
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,15 @@ EXPORTS
HCWebSocketGetHeader
HCWebSocketGetHeaderAtIndex
HCWebSocketGetNumHeaders
HCWebSocketGetPingInterval
HCWebSocketGetProxyUri
HCWebSocketSendBinaryMessageAsync
HCWebSocketSendMessageAsync
HCWebSocketSetBinaryMessageFragmentEventFunction
HCWebSocketSetHeader
HCWebSocketSetMaxReceiveBufferSize
HCWebSocketSetProxyDecryptsHttps
HCWebSocketSetPingInterval
HCWebSocketSetProxyUri
XAsyncBegin
XAsyncCancel
Expand Down
13 changes: 12 additions & 1 deletion Include/httpClient/httpClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ typedef void
/// <remarks>
/// WebSocket usage:<br />
/// Create a WebSocket handle using HCWebSocketCreate()<br />
/// Call HCWebSocketSetProxyUri() and HCWebSocketSetHeader() to prepare the HCWebsocketHandle<br />
/// Call HCWebSocketSetProxyUri(), HCWebSocketSetHeader(), or HCWebSocketSetPingInterval() to prepare the HCWebsocketHandle<br />
/// Call HCWebSocketConnectAsync() to connect the WebSocket using the HCWebsocketHandle.<br />
/// Call HCWebSocketSendMessageAsync() to send a message to the WebSocket using the HCWebsocketHandle.<br />
/// Call HCWebSocketDisconnect() to disconnect the WebSocket using the HCWebsocketHandle.<br />
Expand Down Expand Up @@ -947,6 +947,17 @@ STDAPI HCWebSocketSetHeader(
_In_z_ const char* headerValue
) noexcept;

/// <summary>
/// Set the ping interval for the WebSocket.
/// <param name="websocket">The handle of the WebSocket.</param>
/// <param name="pingIntervalSeconds">The interval at which this websocket should send keepalive frames, in seconds.</param>
/// <returns>Result code for this API operation. Possible values are S_OK, E_INVALIDARG, or E_UNEXPECTED.</returns>
/// </summary>
STDAPI HCWebSocketSetPingInterval(
_In_ HCWebsocketHandle websocket,
_In_ uint32_t pingIntervalSeconds
) noexcept;

/// <summary>
/// Gets the WebSocket functions to allow callers to respond to incoming messages and WebSocket close events.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions Include/httpClient/httpProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,18 @@ HCWebSocketGetHeaderAtIndex(
_Out_ const char** headerValue
) noexcept;

/// <summary>
/// Gets the ping interval for this WebSocket.
/// </summary>
/// <param name="websocket">The handle of the WebSocket.</param>
/// <param name="pingIntervalSeconds">The ping interval of this WebSocket.</param>
/// <returns>Result code for this API operation. Possible values are S_OK, or E_INVALIDARG.</returns>
STDAPI
HCWebSocketGetPingInterval(
_In_ HCWebsocketHandle websocket,
_Out_ uint32_t* pingIntervalSeconds
) noexcept;

#endif // !HC_NOWEBSOCKETS

}
2 changes: 1 addition & 1 deletion Source/HTTP/Curl/CurlMulti.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ HRESULT CurlMulti::Perform() noexcept
{
// Reschedule Perform if there are still running requests
int workAvailable{ 0 };
#if HC_PLATFORM == HC_PLATFORM_GDK || LIBCURL_VERSION_NUM >= 0x074201
#if HC_PLATFORM == HC_PLATFORM_GDK || CURL_AT_LEAST_VERSION(7,66,0)
result = curl_multi_poll(m_curlMultiHandle, nullptr, 0, POLL_TIMEOUT_MS, &workAvailable);
#else
result = curl_multi_wait(m_curlMultiHandle, nullptr, 0, POLL_TIMEOUT_MS, &workAvailable);
Expand Down
2 changes: 1 addition & 1 deletion Source/HTTP/Curl/CurlProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ HRESULT HrFromCurlm(CURLMcode c) noexcept
switch (c)
{
case CURLMcode::CURLM_OK: return S_OK;
#if HC_PLATFORM == HC_PLATFORM_GDK || LIBCURL_VERSION_NUM >= 0x074201
#if HC_PLATFORM == HC_PLATFORM_GDK || CURL_AT_LEAST_VERSION(7,69,0)
case CURLMcode::CURLM_BAD_FUNCTION_ARGUMENT: assert(false); return E_INVALIDARG;
#endif
default: return E_FAIL;
Expand Down
10 changes: 9 additions & 1 deletion Source/HTTP/WinHttp/winhttp_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1633,8 +1633,16 @@ void WinHttpConnection::callback_websocket_status_headers_available(
return;
}

DWORD keepAliveMs = std::min<DWORD>(winHttpConnection->m_websocketHandle->websocket->PingInterval() * 1000, WINHTTP_WEB_SOCKET_MIN_KEEPALIVE_VALUE);
bool status = WinHttpSetOption(winHttpConnection->m_hRequest, WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL, (LPVOID)&keepAliveMs, sizeof(DWORD));
if (!status)
{
DWORD dwError = GetLastError();
HC_TRACE_ERROR(HTTPCLIENT, "WinHttpConnection [ID %llu] [TID %ul] WinHttpSetOption errrocode %d", TO_ULL(HCHttpCallGetId(winHttpConnection->m_call)), GetCurrentThreadId(), dwError);
}

constexpr DWORD closeTimeoutMs = 1000;
bool status = WinHttpSetOption(winHttpConnection->m_hRequest, WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT, (LPVOID)&closeTimeoutMs, sizeof(DWORD));
status = WinHttpSetOption(winHttpConnection->m_hRequest, WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT, (LPVOID)&closeTimeoutMs, sizeof(DWORD));
if (!status)
{
DWORD dwError = GetLastError();
Expand Down
50 changes: 49 additions & 1 deletion Source/WebSocket/Android/AndroidWebSocketProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct HttpClientWebSocket

HttpClientWebSocket(JavaVM* vm, jclass webSocketClass, okhttp_websocket_impl* owner)
: m_vm(vm)
, m_setPingInterval(GetSetPingIntervalMethod(GetEnv(vm), webSocketClass))
, m_addHeader(GetAddHeaderMethod(GetEnv(vm), webSocketClass))
, m_connect(GetConnectMethod(GetEnv(vm), webSocketClass))
, m_sendMessage(GetSendMessageMethod(GetEnv(vm), webSocketClass))
Expand All @@ -63,6 +64,23 @@ struct HttpClientWebSocket
}
}

HRESULT SetPingInterval(uint32_t pingInterval) const
{
JNIEnv* env = GetEnv(m_vm);
if (!env || !m_webSocket || !m_setPingInterval)
{
return E_UNEXPECTED;
}

env->CallVoidMethod(m_webSocket, m_setPingInterval, static_cast<jlong>(pingInterval));
if (HadException(env))
{
return E_UNEXPECTED;
}

return S_OK;
}

HRESULT AddHeader(const char* name, const char* value) const
{
if (!name || !value)
Expand Down Expand Up @@ -223,6 +241,22 @@ struct HttpClientWebSocket
return static_cast<JNIEnv*>(env);
}

static jmethodID GetSetPingIntervalMethod(JNIEnv* env, jclass webSocketClass)
{
if (!env || !webSocketClass)
{
return nullptr;
}

const jmethodID setPingInterval = env->GetMethodID(webSocketClass, "setPingInterval", "(J)V");
if (HadException(env) || !setPingInterval)
{
return nullptr;
}

return setPingInterval;
}

static jmethodID GetAddHeaderMethod(JNIEnv* env, jclass webSocketClass)
{
if (!env || !webSocketClass)
Expand Down Expand Up @@ -358,6 +392,7 @@ struct HttpClientWebSocket

private:
JavaVM* const m_vm;
const jmethodID m_setPingInterval;
const jmethodID m_addHeader;
const jmethodID m_connect;
const jmethodID m_sendMessage;
Expand Down Expand Up @@ -691,8 +726,21 @@ struct okhttp_websocket_impl : hc_websocket_impl, std::enable_shared_from_this<o
return E_HC_CONNECT_ALREADY_CALLED;
}

uint32_t pingInterval = 0;
HRESULT hr = HCWebSocketGetPingInterval(m_handle, &pingInterval);
if (FAILED(hr))
{
return hr;
}

hr = m_javaWebSocket.SetPingInterval(pingInterval);
if (FAILED(hr))
{
return hr;
}

uint32_t headerCount = 0;
HRESULT hr = HCWebSocketGetNumHeaders(m_handle, &headerCount);
hr = HCWebSocketGetNumHeaders(m_handle, &headerCount);
if (FAILED(hr))
{
return hr;
Expand Down
15 changes: 14 additions & 1 deletion Source/WebSocket/Websocketpp/websocketpp_websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,9 @@ struct wspp_websocket_impl : public hc_websocket_impl, public std::enable_shared

auto &client = m_client->impl<WebsocketConfigType>();

const long pingIntervalMs = m_hcWebsocketHandle->websocket->PingInterval() * 1000;
client.set_pong_timeout(pingIntervalMs); // default ping interval is 0, which disables the timeout

client.init_asio();
client.start_perpetual();

Expand Down Expand Up @@ -481,6 +484,11 @@ struct wspp_websocket_impl : public hc_websocket_impl, public std::enable_shared
});
});

client.set_pong_timeout_handler([sharedThis](websocketpp::connection_hdl, std::string)
{
sharedThis->close(HCWebSocketCloseStatus::PolicyViolation);
});

// Set User Agent specified by the user. This needs to happen before any connection is created
const auto& headers = m_hcWebsocketHandle->websocket->Headers();

Expand Down Expand Up @@ -841,6 +849,11 @@ struct wspp_websocket_impl : public hc_websocket_impl, public std::enable_shared
// is terminated (i.e. by disconnecting the network cable). Sending periodic ping
// allows us to detect this situation. See https://github.com/zaphoyd/websocketpp/issues/695.

// Preserving behavior: if client did not specify a ping interval, default to WSPP_PING_INTERVAL
const uint64_t pingDelayInMs = m_hcWebsocketHandle->websocket->PingInterval()
? m_hcWebsocketHandle->websocket->PingInterval() * 1000
: WSPP_PING_INTERVAL_MS;

RunAsync(
[
weakThis = std::weak_ptr<wspp_websocket_impl>{ shared_from_this() }
Expand Down Expand Up @@ -874,7 +887,7 @@ struct wspp_websocket_impl : public hc_websocket_impl, public std::enable_shared
}
},
m_backgroundQueue,
WSPP_PING_INTERVAL_MS
pingDelayInMs
);
}

Expand Down
11 changes: 11 additions & 0 deletions Source/WebSocket/hcwebsocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ size_t WebSocket::MaxReceiveBufferSize() const noexcept
return m_maxReceiveBufferSize;
}

uint32_t WebSocket::PingInterval() const noexcept
{
return m_pingInterval;
}

HRESULT WebSocket::SetHeader(
http_internal_string&& headerName,
http_internal_string&& headerValue
Expand Down Expand Up @@ -408,6 +413,12 @@ HRESULT WebSocket::SetMaxReceiveBufferSize(size_t maxReceiveBufferSizeBytes) noe
return S_OK;
}

HRESULT WebSocket::SetPingInterval(uint32_t pingInterval) noexcept
{
m_pingInterval = pingInterval;
return S_OK;
}

void CALLBACK WebSocket::MessageFunc(
HCWebsocketHandle handle,
const char* message,
Expand Down
3 changes: 3 additions & 0 deletions Source/WebSocket/hcwebsocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,13 @@ class WebSocket : public std::enable_shared_from_this<WebSocket>
const http_internal_string& ProxyUri() const noexcept;
const bool ProxyDecryptsHttps() const noexcept;
size_t MaxReceiveBufferSize() const noexcept;
uint32_t PingInterval() const noexcept;

HRESULT SetHeader(http_internal_string&& headerName, http_internal_string&& headerValue) noexcept;
HRESULT SetProxyUri(http_internal_string&& proxyUri) noexcept;
HRESULT SetProxyDecryptsHttps(bool allowProxyToDecryptHttps) noexcept;
HRESULT SetMaxReceiveBufferSize(size_t maxReceiveBufferSizeBytes) noexcept;
HRESULT SetPingInterval(uint32_t pingInterval) noexcept;

// Event functions
static void CALLBACK MessageFunc(HCWebsocketHandle handle, const char* message, void* context);
Expand Down Expand Up @@ -165,6 +167,7 @@ class WebSocket : public std::enable_shared_from_this<WebSocket>
http_internal_string m_uri;
http_internal_string m_subProtocol;
size_t m_maxReceiveBufferSize{ 0 };
uint32_t m_pingInterval{ 0 };

struct ConnectContext;
struct ProviderContext;
Expand Down
23 changes: 23 additions & 0 deletions Source/WebSocket/websocket_publics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ try
}
CATCH_RETURN()

STDAPI HCWebSocketSetPingInterval(
_In_ HCWebsocketHandle handle,
_In_ uint32_t pingIntervalSeconds
) noexcept
try
{
RETURN_HR_IF(E_INVALIDARG, !handle);
return handle->websocket->SetPingInterval(pingIntervalSeconds);
}
CATCH_RETURN()

STDAPI HCWebSocketConnectAsync(
_In_z_ const char* uri,
Expand Down Expand Up @@ -282,6 +292,19 @@ try
}
CATCH_RETURN()

STDAPI HCWebSocketGetPingInterval(
_In_ HCWebsocketHandle handle,
_Out_ uint32_t* pingIntervalSeconds
) noexcept
try
{
RETURN_HR_IF(E_INVALIDARG, !handle || !pingIntervalSeconds);

*pingIntervalSeconds = handle->websocket->PingInterval();
return S_OK;
}
CATCH_RETURN()

STDAPI HCWebSocketGetEventFunctions(
_In_ HCWebsocketHandle websocket,
_Out_opt_ HCWebSocketMessageFunction* messageFunc,
Expand Down
2 changes: 2 additions & 0 deletions Utilities/FrameworkResources/exports.exp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ _HCHttpCallResponseGetResponseBodyWriteFunction
_HCHttpCallResponseSetGzipCompressed

_HCWebSocketCreate
_HCWebSocketSetPingInterval
_HCWebSocketSetProxyUri
_HCWebSocketSetHeader
_HCWebSocketGetEventFunctions
_HCWebSocketGetPingInterval
_HCWebSocketConnectAsync
_HCGetWebSocketConnectResult
_HCWebSocketSendMessageAsync
Expand Down