Files
handleaiuinew/env/LMI/include/kApi/Io/kHttpServerChannel.cpp
T
2024-05-18 23:53:26 +08:00

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;
}