317 lines
9.4 KiB
C++
317 lines
9.4 KiB
C++
/**
|
|
* @file kHttpServerChannel.cpp
|
|
*
|
|
* @internal
|
|
* Copyright (C) 2013-2021 by LMI Technologies Inc.
|
|
* Licensed under the MIT License.
|
|
* Redistributed files must retain the above copyright notice.
|
|
*/
|
|
#include <kApi/Io/kHttpServerChannel.h>
|
|
#include <kApi/Io/kHttpServer.h>
|
|
#include <kApi/Io/kHttpServerRequest.h>
|
|
#include <kApi/Io/kHttpServerResponse.h>
|
|
#include <kApi/Io/kTcpClient.h>
|
|
#include <kApi/Io/kWebSocket.h>
|
|
#include <kApi/Threads/kLock.h>
|
|
#include <kApi/Threads/kThread.h>
|
|
|
|
kBeginClassEx(k, kHttpServerChannel)
|
|
kAddPrivateVMethod(kHttpServerChannel, kObject, VRelease)
|
|
kEndClassEx()
|
|
|
|
kFx(kStatus) xkHttpServerChannel_Construct(kHttpServerChannel* channel, kHttpServer server, kTcpClient client, kAlloc allocator)
|
|
{
|
|
kAlloc alloc = kAlloc_Fallback(allocator);
|
|
kType type = kTypeOf(kHttpServerChannel);
|
|
kStatus status;
|
|
|
|
kCheck(kAlloc_GetObject(alloc, type, channel));
|
|
|
|
if (!kSuccess(status = xkHttpServerChannel_Init(*channel, type, server, client, alloc)))
|
|
{
|
|
kAlloc_FreeRef(alloc, channel);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_Init(kHttpServerChannel channel, kType type, kHttpServer server, kTcpClient client, kAlloc allocator)
|
|
{
|
|
kObjR(kHttpServerChannel, channel);
|
|
kStatus status;
|
|
|
|
kCheck(kObject_Init(channel, type, allocator));
|
|
|
|
obj->server = server;
|
|
obj->client = client;
|
|
obj->clientLock = kNULL;
|
|
obj->thread = kNULL;
|
|
obj->request = kNULL;
|
|
obj->response = kNULL;
|
|
|
|
kTry
|
|
{
|
|
kTest(kLock_Construct(&obj->clientLock, allocator));
|
|
|
|
kTest(xkHttpServerRequest_Construct(&obj->request, channel, allocator));
|
|
kTest(xkHttpServerResponse_Construct(&obj->response, channel, allocator));
|
|
|
|
//start thread to process incoming http requests
|
|
kTest(kThread_Construct(&obj->thread, allocator));
|
|
kTest(kThread_StartEx(obj->thread, xkHttpServerChannel_ThreadEntry, channel, 0, "kHttpServerChannel", 0));
|
|
}
|
|
kCatch(&status)
|
|
{
|
|
xkHttpServerChannel_VRelease(channel);
|
|
kEndCatch(status);
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_VRelease(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
//abort any ongoing communication operations
|
|
kCheck(xkHttpServerChannel_CancelClient(channel));
|
|
|
|
kCheck(kObject_Destroy(obj->thread));
|
|
kCheck(kObject_Destroy(obj->client));
|
|
kCheck(kObject_Destroy(obj->request));
|
|
kCheck(kObject_Destroy(obj->response));
|
|
kCheck(kObject_Destroy(obj->clientLock));
|
|
|
|
kCheck(kObject_VRelease(channel));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_ThreadEntry(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
kStatus result;
|
|
|
|
//process messages until:
|
|
//a) the user-provided callback requests close ('connection: closed'), in which case obj->client will be null
|
|
//b) the channel stream is detached by the user-provided callback, in which case obj->client will be null
|
|
//c) the channel stream is closed unexpectedly by the client, in which case 'result' will most likely be kERROR_CLOSED
|
|
//d) the channel stream is cancelled by the server (parent), in which case 'result' will most likely be kERROR_ABORT
|
|
//e) an unexpected/unhandled error occurs
|
|
do
|
|
{
|
|
result = xkHttpServerChannel_ProcessMessage(channel);
|
|
}
|
|
while (kSuccess(result) && !kIsNull(obj->client));
|
|
|
|
//ensure that the channel stream is closed, if it isn't already
|
|
kCheck(xkHttpServerChannel_DestroyClient(channel));
|
|
|
|
return result;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_ProcessMessage(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
kCallback handler = xkHttpServer_Handler(obj->server);
|
|
kStatus status = kOK;
|
|
|
|
//process request line and headers
|
|
kCheck(xkHttpServerRequest_Begin(obj->request));
|
|
|
|
//if requested, automatically send a 100-continue message
|
|
if (xkHttpServerRequest_ExpectsContinue(obj->request))
|
|
{
|
|
kCheck(xkHttpServerChannel_SendContinue(channel));
|
|
}
|
|
|
|
//initialize response
|
|
kCheck(xkHttpServerResponse_Begin(obj->response));
|
|
|
|
//invoke user callback to perform message processing
|
|
status = handler.function(handler.receiver, obj->server, channel);
|
|
|
|
//if the stream wasn't detached by the user callback...
|
|
if (!kIsNull(obj->client))
|
|
{
|
|
if (kSuccess(status))
|
|
{
|
|
//if processing completed normally, finalize the response
|
|
kCheck(xkHttpServerResponse_End(obj->response));
|
|
}
|
|
else if (!xkHttpServerResponse_MessageStarted(obj->response) && kSuccess(kTcpClient_Status(obj->client)))
|
|
{
|
|
//if an unhandled processing error occurred, the stream is still valid, and response headers haven't
|
|
//already been transmitted, discard any partially-formatted response and send an 'internal server error'
|
|
//message instead
|
|
kCheck(xkHttpServerResponse_Begin(obj->response));
|
|
kCheck(kHttpServerResponse_SetStatus(obj->response, kHTTP_STATUS_INTERNAL_SERVER_ERROR));
|
|
kCheck(kHttpServerResponse_SetClosed(obj->response, kTRUE));
|
|
kCheck(xkHttpServerResponse_End(obj->response));
|
|
}
|
|
|
|
//if the response included a 'connection: closed' header, close the tcp connection here
|
|
if (xkHttpServerResponse_Closed(obj->response))
|
|
{
|
|
kCheck(xkHttpServerChannel_DestroyClient(channel));
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_SendContinue(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
kCheck(xkHttpServerResponse_Begin(obj->response));
|
|
kCheck(kHttpServerResponse_SetStatus(obj->response, kHTTP_STATUS_CONTINUE));
|
|
kCheck(xkHttpServerResponse_End(obj->response));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kHttpServerRequest) kHttpServerChannel_Request(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
return obj->request;
|
|
}
|
|
|
|
kFx(kHttpServerResponse) kHttpServerChannel_Response(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
return obj->response;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_CancelClient(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
//check client lock for null because this function can be called during init failure clean-up
|
|
if (!kIsNull(obj->clientLock))
|
|
{
|
|
kLock_Enter(obj->clientLock);
|
|
{
|
|
if (!kIsNull(obj->client))
|
|
{
|
|
kTcpClient_Cancel(obj->client);
|
|
}
|
|
}
|
|
kLock_Exit(obj->clientLock);
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_DestroyClient(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
kLock_Enter(obj->clientLock);
|
|
{
|
|
kDestroyRef(&obj->client);
|
|
}
|
|
kLock_Exit(obj->clientLock);
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kHttpServerChannel_DetachClient(kHttpServerChannel channel, kTcpClient* client)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
//finalize the outgoing response before surrendering control of the client
|
|
kCheck(xkHttpServerResponse_End(obj->response));
|
|
|
|
kLock_Enter(obj->clientLock);
|
|
{
|
|
*client = obj->client;
|
|
obj->client = kNULL;
|
|
}
|
|
kLock_Exit(obj->clientLock);
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kBool) xkHttpServerChannel_IsClosed(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
return kSuccess(kThread_Join(obj->thread, 0, kNULL));
|
|
}
|
|
|
|
kFx(kTcpClient) xkHttpServerChannel_Client(kHttpServerChannel channel)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
|
|
return obj->client;
|
|
}
|
|
|
|
kFx(kStatus) xkHttpServerChannel_NormalizeHeaderCaps(kChar* str)
|
|
{
|
|
if (*str != 0)
|
|
{
|
|
*str = kChar_ToUpper(*str);
|
|
|
|
while (!kIsNull(str = strchr(str, '-')))
|
|
{
|
|
str++;
|
|
|
|
if (*str != 0)
|
|
{
|
|
*str = kChar_ToUpper(*str);
|
|
}
|
|
}
|
|
}
|
|
|
|
return kOK;
|
|
}
|
|
|
|
kFx(kStatus) kHttpServerChannel_ProcessWebSocketRequest(kHttpServerChannel channel, kBool shouldAccept, kWebSocket* webSocket)
|
|
{
|
|
kObj(kHttpServerChannel, channel);
|
|
kHttpServerResponse request = kHttpServerChannel_Request(channel);
|
|
kHttpServerResponse response = kHttpServerChannel_Response(channel);
|
|
const kChar* headerValue = kNULL;
|
|
k32s version;
|
|
|
|
if (kIsNull(headerValue = kHttpServerRequest_FindHeaderValue(request, "Sec-WebSocket-Version")) ||
|
|
!kSuccess(k32s_Parse(&version, headerValue)))
|
|
{
|
|
kCheck(kHttpServerResponse_SetStatus(response, kHTTP_STATUS_BAD_REQUEST));
|
|
kCheck(kHttpServerResponse_SetClosed(response, kTRUE));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
if (version != xkWEB_SOCKET_PROTOCOL_VERSION)
|
|
{
|
|
kCheck(kHttpServerResponse_SetStatus(response, kHTTP_STATUS_UPGRADE_REQUIRED));
|
|
kCheck(kHttpServerResponse_SetHeader(response, " Sec-WebSocket-Version", "13"));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
if (kIsNull(headerValue = kHttpServerRequest_FindHeaderValue(request, "Sec-WebSocket-Key")))
|
|
{
|
|
kCheck(kHttpServerResponse_SetStatus(response, kHTTP_STATUS_BAD_REQUEST));
|
|
kCheck(kHttpServerResponse_SetClosed(response, kTRUE));
|
|
|
|
return kOK;
|
|
}
|
|
|
|
if (shouldAccept)
|
|
{
|
|
return xkWebSocket_ConstructFromRequest(webSocket, channel, kNULL);
|
|
}
|
|
else
|
|
{
|
|
kCheck(kHttpServerResponse_SetStatus(obj->response, kHTTP_STATUS_BAD_REQUEST));
|
|
kCheck(kHttpServerResponse_SetClosed(obj->response, kTRUE));
|
|
}
|
|
|
|
return kOK;
|
|
}
|