/** * @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 #include #include #include #include #include #include #include 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; }