594 lines
16 KiB
C++
Raw Normal View History

2024-10-19 22:17:10 +08:00
// https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/netds/http/HttpV2Server/main.c
/*++
Copyright (c) 2002 - 2002 Microsoft Corporation. All Rights Reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS-IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
THIS CODE IS NOT SUPPORTED BY MICROSOFT.
--*/
#define SECURITY_WIN32
#include <http.h>
#include <sspi.h>
#include <strsafe.h>
#define NUM_SCHEMES 2
#define MAX_USERNAME_LENGTH 100
#pragma warning(disable : 4127) // condition expression is constant
//
// Macros.
//
#define INITIALIZE_HTTP_RESPONSE(resp, status, reason) \
do \
{ \
RtlZeroMemory((resp), sizeof(*(resp))); \
(resp)->StatusCode = (status); \
(resp)->pReason = (reason); \
(resp)->ReasonLength = (USHORT)strlen(reason); \
} while (FALSE)
#define ADD_KNOWN_HEADER(Response, HeaderId, RawValue) \
do \
{ \
(Response).Headers.KnownHeaders[(HeaderId)].pRawValue = (RawValue); \
(Response).Headers.KnownHeaders[(HeaderId)].RawValueLength = \
(USHORT)strlen(RawValue); \
} while (FALSE)
#define ALLOC_MEM(cb) HeapAlloc(GetProcessHeap(), 0, (cb))
#define FREE_MEM(ptr) HeapFree(GetProcessHeap(), 0, (ptr))
//
// Prototypes.
//
DWORD
DoReceiveRequests(
HANDLE hReqQueue);
DWORD
SendHttpResponse(
IN HANDLE hReqQueue,
IN PHTTP_REQUEST pRequest);
/***************************************************************************++
Routine Description:
main routine.
Arguments:
argc - # of command line arguments.
argv - Arguments.
Return Value:
Success/Failure.
--***************************************************************************/
int cleanuphttp(HANDLE hReqQueue, HTTP_SERVER_SESSION_ID ssID, HTTP_URL_GROUP_ID urlGroupId)
{
ULONG retCode;
//
// Call HttpRemoveUrl for all the URLs that we added.
// HTTP_URL_FLAG_REMOVE_ALL flag allows us to remove
// all the URLs registered on URL Group at once
//
if (!HTTP_IS_NULL_ID(&urlGroupId))
{
retCode = HttpRemoveUrlFromUrlGroup(urlGroupId,
NULL,
HTTP_URL_FLAG_REMOVE_ALL);
}
//
// Close the Url Group
//
if (!HTTP_IS_NULL_ID(&urlGroupId))
{
retCode = HttpCloseUrlGroup(urlGroupId);
}
//
// Close the serversession
//
if (!HTTP_IS_NULL_ID(&urlGroupId))
{
retCode = HttpCloseServerSession(ssID);
}
//
// Close the Request Queue handle.
//
if (hReqQueue)
{
retCode = HttpCloseRequestQueue(hReqQueue);
}
//
// Call HttpTerminate.
//
HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
return retCode;
}
auto makeserveronce(int port)
{
ULONG retCode;
HANDLE hReqQueue = NULL;
HTTP_SERVER_SESSION_ID ssID = HTTP_NULL_ID;
HTTP_URL_GROUP_ID urlGroupId = HTTP_NULL_ID;
HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_2;
HTTP_BINDING_INFO BindingProperty;
HTTP_TIMEOUT_LIMIT_INFO CGTimeout;
auto url = std::wstring(L"http://127.0.0.1:") + std::to_wstring(port) + L"/fuck";
//
// Initialize HTTP APIs.
//
retCode = HttpInitialize(
HttpApiVersion,
HTTP_INITIALIZE_SERVER, // Flags
NULL // Reserved
);
if (retCode != NO_ERROR)
{
return std::tuple{false, hReqQueue, ssID, urlGroupId};
}
//
// Create a server session handle
//
retCode = HttpCreateServerSession(HttpApiVersion,
&ssID,
0);
if (retCode != NO_ERROR)
{
return std::tuple{false, hReqQueue, ssID, urlGroupId};
}
//
// Create UrlGroup handle
//
retCode = HttpCreateUrlGroup(ssID,
&urlGroupId,
0);
if (retCode != NO_ERROR)
{
return std::tuple{false, hReqQueue, ssID, urlGroupId};
}
//
// Create a request queue handle
//
retCode = HttpCreateRequestQueue(HttpApiVersion,
(std::wstring(L"LUNA_INTERNAL_HTTP_QUEUE") + std::to_wstring(GetCurrentProcessId()) + L"_" + std::to_wstring(rand())).c_str(),
NULL,
0,
&hReqQueue);
if (retCode != NO_ERROR)
{
return std::tuple{false, hReqQueue, ssID, urlGroupId};
}
BindingProperty.Flags.Present = 1; // Specifies that the property is present on UrlGroup
BindingProperty.RequestQueueHandle = hReqQueue;
//
// Bind the request queue to UrlGroup
//
retCode = HttpSetUrlGroupProperty(urlGroupId,
HttpServerBindingProperty,
&BindingProperty,
sizeof(BindingProperty));
if (retCode != NO_ERROR)
{
return std::tuple{false, hReqQueue, ssID, urlGroupId};
}
//
// Set EntityBody Timeout property on UrlGroup
//
ZeroMemory(&CGTimeout, sizeof(HTTP_TIMEOUT_LIMIT_INFO));
CGTimeout.Flags.Present = 1; // Specifies that the property is present on UrlGroup
CGTimeout.EntityBody = 50; // The timeout is in secs
retCode = HttpSetUrlGroupProperty(urlGroupId,
HttpServerTimeoutsProperty,
&CGTimeout,
sizeof(HTTP_TIMEOUT_LIMIT_INFO));
if (retCode != NO_ERROR)
{
return std::tuple{false, hReqQueue, ssID, urlGroupId};
}
//
// Add the URLs on URL Group
// The command line arguments represent URIs that we want to listen on.
// We will call HttpAddUrlToUrlGroup for each of these URIs.
//
// The URI is a fully qualified URI and MUST include the terminating '/'
//
retCode = HttpAddUrlToUrlGroup(urlGroupId,
url.c_str(),
0,
0);
if (retCode != NO_ERROR)
{
return std::tuple{false, hReqQueue, ssID, urlGroupId};
}
return std::tuple{true, hReqQueue, ssID, urlGroupId};
}
int GetRandomAvailablePort()
{
2024-10-20 18:15:15 +08:00
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0)
{
return 0;
}
// 创建一个 TCP 套接字
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET)
{
WSACleanup();
return 0;
}
// 绑定到随机端口
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = 0; // 0 表示让系统自动选择一个可用端口
result = bind(sock, (SOCKADDR *)&addr, sizeof(addr));
if (result == SOCKET_ERROR)
{
closesocket(sock);
WSACleanup();
return 0;
}
// 获取实际绑定的端口号
int addrLen = sizeof(addr);
result = getsockname(sock, (SOCKADDR *)&addr, &addrLen);
if (result == SOCKET_ERROR)
{
closesocket(sock);
WSACleanup();
return 0;
}
// 关闭套接字
closesocket(sock);
WSACleanup();
// 返回实际绑定的端口号
return ntohs(addr.sin_port);
2024-10-19 22:17:10 +08:00
}
int makehttpgetserverinternal()
{
2024-10-20 00:10:15 +08:00
// 尝试1000次
for (int i = 0; i < 1000; i++)
2024-10-19 22:17:10 +08:00
{
auto port = GetRandomAvailablePort();
auto [succ, hReqQueue, ssID, urlGroupId] = makeserveronce(port);
if (!succ)
{
cleanuphttp(hReqQueue, ssID, urlGroupId);
continue;
}
std::thread([=]()
{
// Loop while receiving requests
DoReceiveRequests(hReqQueue);
cleanuphttp(hReqQueue, ssID, urlGroupId); })
.detach();
return port;
}
2024-10-20 00:10:15 +08:00
return 0;
2024-10-19 22:17:10 +08:00
}
// int main()
// {
// wprintf(L"%d", makehttpgetserverinternal());
// Sleep(999999);
// }
/***************************************************************************++
Routine Description:
The routine to receive a request. This routine calls the corresponding
routine to deal with the response.
Arguments:
hReqQueue - Handle to the request queue.
Return Value:
Success/Failure.
--***************************************************************************/
DWORD
DoReceiveRequests(
IN HANDLE hReqQueue)
{
ULONG result;
HTTP_REQUEST_ID requestId;
DWORD bytesRead;
PHTTP_REQUEST pRequest;
PCHAR pRequestBuffer;
ULONG RequestBufferLength;
//
// Allocate a 2K buffer. Should be good for most requests, we'll grow
// this if required. We also need space for a HTTP_REQUEST structure.
//
RequestBufferLength = sizeof(HTTP_REQUEST) + 2048;
pRequestBuffer = (PCHAR)ALLOC_MEM(RequestBufferLength);
if (pRequestBuffer == NULL)
{
return ERROR_NOT_ENOUGH_MEMORY;
}
pRequest = (PHTTP_REQUEST)pRequestBuffer;
//
// Wait for a new request -- This is indicated by a NULL request ID.
//
HTTP_SET_NULL_ID(&requestId);
for (;;)
{
RtlZeroMemory(pRequest, RequestBufferLength);
result = HttpReceiveHttpRequest(
hReqQueue, // Req Queue
requestId, // Req ID
0, // Flags
pRequest, // HTTP request buffer
RequestBufferLength, // req buffer length
&bytesRead, // bytes received
NULL // LPOVERLAPPED
);
if (NO_ERROR == result)
{
//
// Worked!
//
// switch (pRequest->Verb)
// {
// case HttpVerbGET:
result = SendHttpResponse(
hReqQueue,
pRequest);
// case HttpVerbPOST:
// default:
// if (result != NO_ERROR)
// {
// break;
// }
//
// Reset the Request ID so that we pick up the next request.
//
HTTP_SET_NULL_ID(&requestId);
}
else if (result == ERROR_MORE_DATA)
{
//
// The input buffer was too small to hold the request headers
// We have to allocate more buffer & call the API again.
//
// When we call the API again, we want to pick up the request
// that just failed. This is done by passing a RequestID.
//
// This RequestID is picked from the old buffer.
//
requestId = pRequest->RequestId;
//
// Free the old buffer and allocate a new one.
//
RequestBufferLength = bytesRead;
FREE_MEM(pRequestBuffer);
pRequestBuffer = (PCHAR)ALLOC_MEM(RequestBufferLength);
if (pRequestBuffer == NULL)
{
result = ERROR_NOT_ENOUGH_MEMORY;
break;
}
pRequest = (PHTTP_REQUEST)pRequestBuffer;
}
else if (ERROR_CONNECTION_INVALID == result &&
!HTTP_IS_NULL_ID(&requestId))
{
// The TCP connection got torn down by the peer when we were
// trying to pick up a request with more buffer. We'll just move
// onto the next request.
HTTP_SET_NULL_ID(&requestId);
}
else
{
break;
}
} // for(;;)
if (pRequestBuffer)
{
FREE_MEM(pRequestBuffer);
}
return result;
}
/***************************************************************************++
Routine Description:
The routine sends a HTTP response.
Arguments:
hReqQueue - Handle to the request queue.
pRequest - The parsed HTTP request.
StatusCode - Response Status Code.
pReason - Response reason phrase.
pEntityString - Response entity body.
Return Value:
Success/Failure.
--***************************************************************************/
#pragma optimize("", off)
const wchar_t *LUNA_CONTENTBYPASS(const wchar_t *_)
{
return _;
}
#pragma optimize("", on)
DWORD
SendHttpResponse(
IN HANDLE hReqQueue,
IN PHTTP_REQUEST pRequest)
{
HTTP_RESPONSE response;
DWORD result;
DWORD bytesSent;
2024-10-20 14:07:21 +08:00
ULONG BytesRead;
HTTP_DATA_CHUNK dataChunk;
std::string recv;
std::string buff;
buff.resize(2048);
bool recving = true;
2024-10-19 22:17:10 +08:00
//
// Initialize the HTTP response structure.
//
2024-10-20 14:07:21 +08:00
INITIALIZE_HTTP_RESPONSE(&response, 200, "OK");
2024-10-19 22:17:10 +08:00
//
2024-10-20 14:07:21 +08:00
// For POST, we'll echo back the entity that we got from the client.
2024-10-19 22:17:10 +08:00
//
2024-10-20 14:07:21 +08:00
// NOTE: If we had passed the HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY
// flag with HttpReceiveHttpRequest(), the entity would have
// been a part of HTTP_REQUEST (using the pEntityChunks field).
// Since we have not passed that flag, we can be assured that
// there are no entity bodies in HTTP_REQUEST.
//
if (pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS)
2024-10-19 22:17:10 +08:00
{
2024-10-20 14:07:21 +08:00
// The entity body is send over multiple calls. Let's collect all
// of these in a file & send it back. We'll create a temp file
2024-10-19 22:17:10 +08:00
//
2024-10-20 14:07:21 +08:00
do
{
//
// Read the entity chunk from the request.
//
BytesRead = 0;
result = HttpReceiveRequestEntityBody(
hReqQueue,
pRequest->RequestId,
0,
buff.data(),
buff.capacity(),
&BytesRead,
NULL);
switch (result)
{
case NO_ERROR:
case ERROR_HANDLE_EOF:
if (BytesRead != 0)
{
recv += buff.substr(0, BytesRead);
}
if (result == ERROR_HANDLE_EOF)
recving = false;
break;
2024-10-19 22:17:10 +08:00
2024-10-20 14:07:21 +08:00
default:
recving = false;
}
2024-10-19 22:17:10 +08:00
2024-10-20 14:07:21 +08:00
} while (recving);
}
if (recv.size())
recv = WideStringToString(LUNA_CONTENTBYPASS(StringToWideString(recv).c_str()));
if (recv.size())
{
ADD_KNOWN_HEADER(
response,
HttpHeaderContentLength,
std::to_string(recv.size()).c_str());
}
result =
HttpSendHttpResponse(
hReqQueue, // ReqQueueHandle
pRequest->RequestId, // Request ID
recv.size() ? HTTP_SEND_RESPONSE_FLAG_MORE_DATA : 0,
&response, // HTTP response
NULL, // pReserved1
&bytesSent, // bytes sent (optional)
NULL, // pReserved2
0, // Reserved3
NULL, // LPOVERLAPPED
NULL // pReserved4
);
if (result != NO_ERROR)
{
return result;
}
if (!recv.size())
return result;
//
// Send entity body from a file handle.
//
dataChunk.DataChunkType = HttpDataChunkFromMemory;
dataChunk.FromMemory.pBuffer = (PVOID)recv.c_str();
dataChunk.FromMemory.BufferLength = (ULONG)recv.size();
result = HttpSendResponseEntityBody(
hReqQueue,
pRequest->RequestId,
0, // This is the last send.
1, // Entity Chunk Count.
&dataChunk,
NULL,
NULL,
0,
NULL,
NULL);
2024-10-19 22:17:10 +08:00
return result;
}