I'm looking to implement a native websocket handler via a native module in IIS. I'm finding the documentation around it to be pretty vague and missing a lot of details.
I've created a IIS Native module DLL and it is working. I can keep it simple and just return a hello world html file with HTTP/200 response, and all is well.
However, I'm running into an issue when attempting to have it handle a websocket connection. There was a blog post on microsoft's IIS blog site here which describes how to handle a websocket connection. I've followed it and have tested and, yes, the connection is opened from a web browser, however I cannot read data from the socket in the native module, and the connection often appears to close in error on the client - after a random amount of time it seems.
The OnBeginRequest method of the module is:
REQUEST_NOTIFICATION_STATUS CKIIS::OnBeginRequest(IN IHttpContext* pHttpContext, IN IHttpEventProvider* pProvider) {
UNREFERENCED_PARAMETER(pProvider);
HRESULT hr;
// I've only placed this here so I can attach a debugger.
std::this_thread::sleep_for(std::chrono::seconds(10));
this->_context = pHttpContext;
IHttpResponse* pHttpResponse = pHttpContext->GetResponse();
if (pHttpResponse != NULL)
{
pHttpResponse->Clear();
pHttpResponse->SetStatus(101, "Switching Protocols");
pHttpResponse->SetHeader(
HttpHeaderUpgrade, "websocket",
(USHORT)strlen("websocket"), TRUE);
pHttpResponse->SetHeader(
HttpHeaderConnection, "Upgrade",
(USHORT)strlen("Upgrade"), TRUE);
DWORD cbSent = 0;
BOOL fCompletionExpected = false;
hr = pHttpResponse->Flush(false, true, &cbSent, &fCompletionExpected);
std::this_thread::sleep_for(std::chrono::seconds(10));
IHttpContext3* pHttpContext3;
HttpGetExtendedInterface(this->_server, pHttpContext, &pHttpContext3);
IWebSocketContext* cts = (IWebSocketContext*)pHttpContext3->GetNamedContextContainer()->GetNamedContext(L"websockets");
char buffer[1024 * 100];
DWORD sz = 1024 * 100;
BOOL utf;
BOOL finalfrag;
BOOL conclose;
DWORD clxc = 78;
BOOL expected;
// This method call returns E_NOTIMPL.
// The documentation does not even indicate this is an expected return of this.
HRESULT res = cts->ReadFragment(
&buffer,
&sz,
false,
&utf,
&finalfrag,
&conclose,
Compl,
&clxc,
&expected);
// Start a thread to read/write from the websocket.
this->_runner = thread(&CKIIS::RunWork, this);
// Tell IIS to keep the connection pending...
return RQ_NOTIFICATION_PENDING;
}
// Return processing to the pipeline.
return RQ_NOTIFICATION_CONTINUE;
}
void CKIIS::RunWork() {
IHttpContext3* pHttpContext3;
HttpGetExtendedInterface(this->_server, this->_context, &pHttpContext3);
IWebSocketContext* cts = (IWebSocketContext*)pHttpContext3->GetNamedContextContainer()->GetNamedContext(L"websockets");
for (;;) {
// Loop to read/write the socket...
// If I call cts->ReadFragment() or cts->WriteFragment() here
// the method will return E_NOTIMPL too.
/// Eventually break out of the loop.
}
try {
//this->_context->IndicateCompletion(RQ_NOTIFICATION_FINISH_REQUEST);
this->_context->PostCompletion(0);
}
catch(const std::exception& e){
const char* barf = e.what();
std::cout << e.what();
}
}
Few questions:
- Why does ReadFragment/WriteFragment return E_NOTIMPL. Both in the OnBeginRequest method, or if I attempt to do it in its own thread.
- Is using a new thread to handle the read/writing to the websocket the preferred pattern here? Having a busy loop like that polling for data seems pretty wasteful, but I cannot see any way to register for notifications when data comes available from the client