/** * @file kHttpServer.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 kBeginClassEx(k, kHttpServer) kAddPrivateVMethod(kHttpServer, kObject, VRelease) kEndClassEx() kFx(kStatus) kHttpServer_Construct(kHttpServer* server, kAlloc allocator) { kAlloc alloc = kAlloc_Fallback(allocator); kType type = kTypeOf(kHttpServer); kStatus status; kCheck(kAlloc_GetObject(alloc, type, server)); if (!kSuccess(status = xkHttpServer_Init(*server, type, alloc))) { kAlloc_FreeRef(alloc, server); } return status; } kFx(kStatus) xkHttpServer_Init(kHttpServer server, kType type, kAlloc allocator) { kObjR(kHttpServer, server); kCheck(kObject_Init(server, type, allocator)); obj->localEndPoint.address = kIpAddress_AnyV4(); obj->localEndPoint.port = xkHTTP_SERVER_DEFAULT_PORT; obj->channelCapacity = xkHTTP_SERVER_DEFAULT_CHANNEL_CAPACITY; obj->socketWriteBufferSize = xkHTTP_SERVER_DEFAULT_SOCKET_WRITE_BUFFER; obj->clientWriteBufferSize = xkHTTP_SERVER_DEFAULT_CLIENT_WRITE_BUFFER; obj->socketReadBufferSize = xkHTTP_SERVER_DEFAULT_SOCKET_READ_BUFFER; obj->clientReadBufferSize = xkHTTP_SERVER_DEFAULT_CLIENT_READ_BUFFER; obj->handler.function = kNULL; obj->handler.receiver = kNULL; obj->listener = kNULL; obj->thread = kNULL; obj->shouldQuit = 0; obj->channels = kNULL; obj->backlog = xkHTTP_SERVER_DEFAULT_LISTEN_BACKLOG; return kOK; } kFx(kStatus) xkHttpServer_VRelease(kHttpServer server) { kHttpServer_Stop(server); kCheck(kObject_VRelease(server)); return kOK; } kFx(kStatus) kHttpServer_SetAddress(kHttpServer server, kIpAddress address) { kObj(kHttpServer, server); kCheckState(kIsNull(obj->thread)); obj->localEndPoint.address = address; return kOK; } kFx(kStatus) kHttpServer_SetPort(kHttpServer server, k32u port) { kObj(kHttpServer, server); kCheckState(kIsNull(obj->thread)); obj->localEndPoint.port = port; return kOK; } kFx(kStatus) kHttpServer_SetMaxConnections(kHttpServer server, kSize capacity) { kObj(kHttpServer, server); kCheckState(kIsNull(obj->thread)); obj->channelCapacity = capacity; return kOK; } kFx(kStatus) kHttpServer_SetBacklog(kHttpServer server, kSize backlog) { kObj(kHttpServer, server); kCheckState(kIsNull(obj->thread)); obj->backlog = backlog; return kOK; } kFx(kStatus) kHttpServer_SetWriteBuffers(kHttpServer server, kSSize socketSize, kSSize clientSize) { kObj(kHttpServer, server); kCheckState(kIsNull(obj->thread)); if (socketSize >= 0) { obj->socketWriteBufferSize = socketSize; } if (clientSize >= 0) { obj->clientWriteBufferSize = clientSize; } return kOK; } kFx(kStatus) kHttpServer_SetReadBuffers(kHttpServer server, kSSize socketSize, kSSize clientSize) { kObj(kHttpServer, server); kCheckState(kIsNull(obj->thread)); if (socketSize >= 0) { obj->socketReadBufferSize = socketSize; } if (clientSize >= 0) { obj->clientReadBufferSize = clientSize; } return kOK; } kFx(kStatus) kHttpServer_SetHandler(kHttpServer server, kCallbackFx function, kPointer receiver) { kObj(kHttpServer, server); kCheckState(kIsNull(obj->thread)); obj->handler.function = function; obj->handler.receiver = receiver; return kOK; } kFx(kStatus) kHttpServer_Start(kHttpServer server) { kObj(kHttpServer, server); kStatus status; kCheckState(kIsNull(obj->thread)); kCheckState(!kIsNull(obj->handler.function)); kAtomic32s_Exchange(&obj->shouldQuit, kFALSE); kTry { kTest(kList_Construct(&obj->channels, kTypeOf(kObject), obj->channelCapacity, kObject_Alloc(server))); //configure server socket kTest(kTcpServer_Construct(&obj->listener, obj->localEndPoint.address.version, kObject_Alloc(server))); kTest(kTcpServer_SetReadBuffers(obj->listener, obj->socketReadBufferSize, obj->clientReadBufferSize)); kTest(kTcpServer_SetWriteBuffers(obj->listener, obj->socketWriteBufferSize, obj->clientWriteBufferSize)); kTest(kTcpServer_EnableReuseAddress(obj->listener, kTRUE)); kTest(kTcpServer_Listen(obj->listener, obj->localEndPoint.address, obj->localEndPoint.port, obj->backlog)); //start thread to accept incoming connections kTest(kThread_Construct(&obj->thread, kObject_Alloc(server))); kTest(kThread_StartEx(obj->thread, xkHttpServer_ThreadEntry, server, 0, "kHttpServer", 0)); } kCatch(&status) { kHttpServer_Stop(server); kEndCatch(status); } return kOK; } kFx(kStatus) kHttpServer_Stop(kHttpServer server) { kObj(kHttpServer, server); if (!kIsNull(obj->thread)) { kAtomic32s_Exchange(&obj->shouldQuit, kTRUE); kCheck(kDestroyRef(&obj->thread)); } kCheck(kDestroyRef(&obj->listener)); kCheck(kDisposeRef(&obj->channels)); return kOK; } kFx(kStatus) xkHttpServer_ThreadEntry(kHttpServer server) { kObj(kHttpServer, server); //run until the server is stopped while (!kAtomic32s_Get(&obj->shouldQuit)) { kTcpClient client = kNULL; kHttpServerChannel channel = kNULL; //wait to accept a new connection; the wait time is limited to xkHTTP_SERVER_QUIT_QUERY_PERIOD, so that the //'shouldQuit' flag is periodically polled if (kSuccess(kTcpServer_Accept(obj->listener, xkHTTP_SERVER_QUIT_QUERY_PERIOD, &client, kObject_Alloc(server)))) { if (!kIsNull(client)) { //construct a new channel object to represent the connection, and add it to the list of active channels if (!kSuccess(xkHttpServerChannel_Construct(&channel, server, client, kObject_Alloc(server)))) { kObject_Destroy(client); } else { xkHttpServer_AddChannel(server, channel); } } } } return kOK; } kFx(kStatus) xkHttpServer_AddChannel(kHttpServer server, kHttpServerChannel channel) { kObj(kHttpServer, server); kStatus status; //add new channel to channel list if (!kSuccess(status = kList_AddT(obj->channels, &channel, kNULL))) { kObject_Destroy(channel); return status; } //remove any closed channels, or channels in excess of max connections kCheck(xkHttpServer_CullChannels(server)); return kOK; } kFx(kStatus) xkHttpServer_CullChannels(kHttpServer server) { kObj(kHttpServer, server); kListItem channelIt = 0; //remove any inactive channels channelIt = kList_First(obj->channels); while (!kIsNull(channelIt)) { kListItem current = channelIt; kHttpServerChannel channel = kList_AsT(obj->channels, current, kObject); channelIt = kList_Next(obj->channels, channelIt); if (xkHttpServerChannel_IsClosed(channel)) { kCheck(kList_Remove(obj->channels, current)); kCheck(kObject_Destroy(channel)); } } //remove oldest remaining channels until less than capacity while (kList_Count(obj->channels) > obj->channelCapacity) { kListItem current = kList_First(obj->channels); kHttpServerChannel channel = kList_AsT(obj->channels, current, kObject); kCheck(kList_Remove(obj->channels, current)); kCheck(kObject_Destroy(channel)); } return kOK; } kFx(kStatus) kHttpServer_LocalEndPoint(kHttpServer server, kIpEndPoint* endPoint) { kObj(kHttpServer, server); kCheckState(!kIsNull(obj->listener)); kCheck(kTcpServer_LocalEndPoint(obj->listener, endPoint)); return kOK; } kFx(kCallback) xkHttpServer_Handler(kHttpServer server) { kObj(kHttpServer, server); return obj->handler; }