Add basic upload support to interop server (#326)

Co-authored-by: Nick Banks <nibanks@microsoft.com>
This commit is contained in:
Anthony Rossi 2020-04-21 19:39:52 -07:00 коммит произвёл GitHub
Родитель 23de1390cd
Коммит df5b9f6b7d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 353 добавлений и 38 удалений

Просмотреть файл

@ -197,6 +197,7 @@ if(QUIC_BUILD_TOOLS)
add_subdirectory(src/tools/interop)
add_subdirectory(src/tools/interopserver)
add_subdirectory(src/tools/ping)
add_subdirectory(src/tools/post)
add_subdirectory(src/tools/reach)
add_subdirectory(src/tools/sample)
add_subdirectory(src/tools/spin)

Просмотреть файл

@ -14,6 +14,7 @@ Abstract:
const QUIC_API_TABLE* MsQuic;
QUIC_SEC_CONFIG* SecurityConfig;
const char* RootFolderPath;
const char* UploadFolderPath;
const QUIC_BUFFER SupportedALPNs[] = {
{ sizeof("hq-27") - 1, (uint8_t*)"hq-27" },
@ -29,7 +30,8 @@ PrintUsage()
printf(" interopserver.exe -listen:<addr or *> -root:<path>"
" [-thumbprint:<cert_thumbprint>] [-name:<cert_name>]"
" [-file:<cert_filepath> AND -key:<cert_key_filepath>]"
" [-port:<####> (def:%u)] [-retry:<0/1> (def:%u)]\n\n",
" [-port:<####> (def:%u)] [-retry:<0/1> (def:%u)]"
" [-upload:<path>]\n\n",
DEFAULT_QUIC_HTTP_SERVER_PORT, DEFAULT_QUIC_HTTP_SERVER_RETRY);
printf("Examples:\n");
@ -67,6 +69,7 @@ main(
EXIT_ON_FAILURE(QuicForceRetry(MsQuic, true));
printf("Enabling forced RETRY on server.\n");
}
TryGetValue(argc, argv, "upload", &UploadFolderPath);
//
// Required parameters.
@ -128,18 +131,23 @@ main(
// HttpRequest
//
HttpRequest::HttpRequest(HttpConnection *connection, HQUIC stream) :
HttpRequest::HttpRequest(HttpConnection *connection, HQUIC stream, bool Unidirectional) :
Connection(connection), QuicStream(stream), File(nullptr),
Shutdown(false), WriteHttp11Header(false)
{
MsQuic->SetCallbackHandler(QuicStream, (void*)QuicCallbackHandler, this);
MsQuic->SetCallbackHandler(
QuicStream,
Unidirectional ?
(void*)QuicUnidiCallbackHandler :
(void*)QuicBidiCallbackHandler,
this);
Connection->AddRef();
}
HttpRequest::~HttpRequest()
{
if (File) {
fclose(File);
fclose(File); // TODO - If POST, abandon/delete file as it wasn't finished.
}
MsQuic->StreamClose(QuicStream);
Connection->Release();
@ -177,16 +185,16 @@ HttpRequest::Process()
return;
}
char fullFilePath[256];
strcpy(fullFilePath, RootFolderPath);
char FullFilePath[256];
strcpy(FullFilePath, RootFolderPath);
if (strcmp("/", PathStart) == 0) {
strcat(fullFilePath, "/index.html");
strcat(FullFilePath, "/index.html");
} else {
strcat(fullFilePath, PathStart);
strcat(FullFilePath, PathStart);
}
printf("[%s] GET '%s'\n", GetRemoteAddr(MsQuic, QuicStream).Address, PathStart);
File = fopen(fullFilePath, "rb"); // In case of failure, SendData still works.
File = fopen(FullFilePath, "rb"); // In case of failure, SendData still works.
SendData();
}
@ -252,9 +260,71 @@ HttpRequest::SendData()
}
}
bool
HttpRequest::ReceiveUniDiData(
_In_ const QUIC_BUFFER* Buffers,
_In_ uint32_t BufferCount
)
{
if (UploadFolderPath == nullptr) {
printf("[%s] Server not configured for POST!\n", GetRemoteAddr(MsQuic, QuicStream).Address);
return false;
}
uint32_t SkipLength = 0;
if (File == nullptr) {
const QUIC_BUFFER* FirstBuffer = Buffers;
if (FirstBuffer->Length < 5 ||
_strnicmp((const char*)FirstBuffer->Buffer, "post ", 5) != 0) {
printf("[%s] Invalid post prefix\n", GetRemoteAddr(MsQuic, QuicStream).Address);
return false;
}
char* FileName = (char*)FirstBuffer->Buffer + 5;
char* FileNameEnd = strstr(FileName, "\r\n");
if (FileNameEnd == nullptr) {
printf("[%s] Invalid post suffix\n", GetRemoteAddr(MsQuic, QuicStream).Address);
return false;
}
*FileNameEnd = '\0'; // We shouldn't be writing to the buffer. Don't imitate this.
if (strstr(FileName, "..") != nullptr) {
printf("[%s] '..' found\n", GetRemoteAddr(MsQuic, QuicStream).Address);
return false;
}
char FullFilePath[256];
if (snprintf(FullFilePath, sizeof(FullFilePath), "%s/%s", UploadFolderPath, FileName) < 0) {
printf("[%s] Invalid path\n", GetRemoteAddr(MsQuic, QuicStream).Address);
return false;
}
printf("[%s] POST '%s'\n", GetRemoteAddr(MsQuic, QuicStream).Address, FileName);
File = fopen(FullFilePath, "wb");
if (!File) {
printf("[%s] Failed to open file\n", GetRemoteAddr(MsQuic, QuicStream).Address);
return false;
}
FileNameEnd += 2; // Skip "\r\n"
SkipLength = (uint32_t)((uint8_t*)FileNameEnd - FirstBuffer->Buffer);
}
for (uint32_t i = 0; i < BufferCount; ++i) {
uint32_t DataLength = Buffers[i].Length - SkipLength;
if (fwrite(Buffers[i].Buffer + SkipLength, 1, DataLength, File) < DataLength) {
printf("[%s] Failed to write file\n", GetRemoteAddr(MsQuic, QuicStream).Address);
return false;
}
SkipLength = 0;
}
return true;
}
QUIC_STATUS
QUIC_API
HttpRequest::QuicCallbackHandler(
HttpRequest::QuicBidiCallbackHandler(
_In_ HQUIC Stream,
_In_opt_ void* Context,
_Inout_ QUIC_STREAM_EVENT* Event
@ -295,3 +365,37 @@ HttpRequest::QuicCallbackHandler(
return QUIC_STATUS_SUCCESS;
}
_Function_class_(QUIC_STREAM_CALLBACK)
QUIC_STATUS
QUIC_API
HttpRequest::QuicUnidiCallbackHandler(
_In_ HQUIC Stream,
_In_opt_ void* Context,
_Inout_ QUIC_STREAM_EVENT* Event
)
{
auto pThis = (HttpRequest*)Context;
switch (Event->Type) {
case QUIC_STREAM_EVENT_RECEIVE:
if (!pThis->ReceiveUniDiData(Event->RECEIVE.Buffers, Event->RECEIVE.BufferCount)) {
pThis->Abort(1); // BUG - Seems like we continue to get receive callbacks!
}
break;
case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
if (pThis->File) {
fclose(pThis->File);
pThis->File = nullptr;
}
break;
case QUIC_STREAM_EVENT_PEER_SEND_ABORTED:
printf("[%s] Peer abort (0x%llx)\n",
GetRemoteAddr(MsQuic, Stream).Address,
Event->PEER_SEND_ABORTED.ErrorCode);
break;
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
delete pThis;
break;
}
return QUIC_STATUS_SUCCESS;
}

Просмотреть файл

@ -66,7 +66,7 @@ struct HttpSendBuffer {
struct HttpConnection;
struct HttpRequest {
HttpRequest(HttpConnection *connection, HQUIC stream);
HttpRequest(HttpConnection *connection, HQUIC stream, bool Unidirectional);
private:
HttpConnection *Connection;
HQUIC QuicStream;
@ -85,10 +85,22 @@ private:
}
void Process();
void SendData();
bool ReceiveUniDiData(
_In_ const QUIC_BUFFER* Buffers,
_In_ uint32_t BufferCount
);
static
QUIC_STATUS
QUIC_API
QuicCallbackHandler(
QuicBidiCallbackHandler(
_In_ HQUIC Stream,
_In_opt_ void* Context,
_Inout_ QUIC_STREAM_EVENT* Event
);
static
QUIC_STATUS
QUIC_API
QuicUnidiCallbackHandler(
_In_ HQUIC Stream,
_In_opt_ void* Context,
_Inout_ QUIC_STREAM_EVENT* Event
@ -126,14 +138,10 @@ private:
HttpConnection *pThis = (HttpConnection*)Context;
switch (Event->Type) {
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED:
if (Event->PEER_STREAM_STARTED.Flags & QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL) {
MsQuic->SetCallbackHandler(
Event->PEER_STREAM_STARTED.Stream,
(void*)HttpUnidirectionalStreamCallback,
nullptr);
} else {
new HttpRequest(pThis, Event->PEER_STREAM_STARTED.Stream);
}
new HttpRequest(
pThis,
Event->PEER_STREAM_STARTED.Stream,
Event->PEER_STREAM_STARTED.Flags & QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL);
break;
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
pThis->Release();
@ -141,24 +149,6 @@ private:
}
return QUIC_STATUS_SUCCESS;
}
static
_Function_class_(QUIC_STREAM_CALLBACK)
QUIC_STATUS
HttpUnidirectionalStreamCallback(
_In_ HQUIC Stream,
_In_opt_ void* /* Context */,
_Inout_ QUIC_STREAM_EVENT* Event
)
{
if (Event->Type == QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE) {
//
// Don't care about anything else on the stream except closing it in
// resposne to shutdown complete.
//
MsQuic->StreamClose(Stream);
}
return QUIC_STATUS_SUCCESS;
}
};
struct HttpServer {

Просмотреть файл

@ -0,0 +1,17 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
set(SOURCES
post.cpp
)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QUIC_CXX_FLAGS}")
add_executable(quicpost ${SOURCES})
target_link_libraries(quicpost msquic platform)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_link_libraries(quicpost
ws2_32 schannel ntdll bcrypt ncrypt crypt32 iphlpapi advapi32)
endif()

203
src/tools/post/post.cpp Normal file
Просмотреть файл

@ -0,0 +1,203 @@
/*++
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Abstract:
Very Simple QUIC HTTP 0.9 POST client.
--*/
#define _CRT_SECURE_NO_WARNINGS 1
#include <msquichelper.h>
extern "C" void QuicTraceRundown(void) { }
#define IO_SIZE (128 * 1024)
#define POST_HEADER_FORMAT "POST %s\r\n"
#define EXIT_ON_FAILURE(x) do { \
auto _Status = x; \
if (QUIC_FAILED(_Status)) { \
printf("%s:%d %s failed!\n", __FILE__, __LINE__, #x); \
exit(1); \
} \
} while (0);
#define ALPN_BUFFER(str) { sizeof(str) - 1, (uint8_t*)str }
const QUIC_BUFFER ALPNs[] = {
ALPN_BUFFER("hq-27"),
ALPN_BUFFER("hq-25")
};
const QUIC_API_TABLE* MsQuic;
const uint32_t CertificateValidationFlags = QUIC_CERTIFICATE_FLAG_DISABLE_CERT_VALIDATION;
uint16_t Port = 4433;
const char* ServerName = "localhost";
const char* FilePath = nullptr;
FILE* File = nullptr;
QUIC_EVENT SendReady;
bool TransferCanceled = false;
_IRQL_requires_max_(PASSIVE_LEVEL)
_Function_class_(QUIC_CONNECTION_CALLBACK)
QUIC_STATUS
QUIC_API
ConnectionHandler(
_In_ HQUIC Connection,
_In_opt_ void* /* Context */,
_Inout_ QUIC_CONNECTION_EVENT* Event
)
{
switch (Event->Type) {
case QUIC_CONNECTION_EVENT_CONNECTED:
printf("Connected\n");
break;
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
printf("Transport Shutdown 0x%x\n", Event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status);
break;
case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER:
printf("Peer Shutdown 0x%llx\n", Event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode);
break;
case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
//printf("Shutdown Complete\n");
MsQuic->ConnectionClose(Connection);
break;
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
_IRQL_requires_max_(PASSIVE_LEVEL)
_Function_class_(QUIC_STREAM_CALLBACK)
QUIC_STATUS
QUIC_API
StreamHandler(
_In_ HQUIC Stream,
_In_opt_ void* /* Context */,
_Inout_ QUIC_STREAM_EVENT* Event
)
{
switch (Event->Type) {
case QUIC_STREAM_EVENT_SEND_COMPLETE:
if (Event->SEND_COMPLETE.Canceled) {
TransferCanceled = true;
printf("Send canceled!\n");
}
QuicEventSet(SendReady);
break;
case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED:
printf("Peer stream recv abort (0x%llx)\n", Event->PEER_RECEIVE_ABORTED.ErrorCode);
break;
case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
MsQuic->ConnectionShutdown(Stream, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0);
MsQuic->StreamClose(Stream);
break;
default:
break;
}
return QUIC_STATUS_SUCCESS;
}
int
QUIC_MAIN_EXPORT
main(
_In_ int argc,
_In_reads_(argc) _Null_terminated_ char* argv[]
)
{
if (argc < 2 || !TryGetValue(argc, argv, "file", &FilePath)) {
printf("Usage: quicpost.exe [-server:<name>] [-ip:<ip>] [-port:<number>] -file:<path>\n");
exit(1);
}
TryGetValue(argc, argv, "server", &ServerName);
TryGetValue(argc, argv, "port", &Port);
QuicPlatformSystemLoad();
QuicPlatformInitialize();
File = fopen(FilePath, "rb");
if (File == nullptr) {
printf("Failed to open file!\n");
exit(1);
}
const char* FileName = strrchr(FilePath, '\\');
if (FileName == nullptr) {
FileName = strrchr(FilePath, '/');
}
if (FileName == nullptr) {
FileName = FilePath; // There was no path in FilePath
} else {
FileName += 1;
}
QuicEventInitialize(&SendReady, FALSE, FALSE);
HQUIC Registration = nullptr;
HQUIC Session = nullptr;
HQUIC Connection = nullptr;
HQUIC Stream = nullptr;
EXIT_ON_FAILURE(MsQuicOpen(&MsQuic));
const QUIC_REGISTRATION_CONFIG RegConfig = { "post", QUIC_EXECUTION_PROFILE_LOW_LATENCY };
EXIT_ON_FAILURE(MsQuic->RegistrationOpen(&RegConfig, &Registration));
EXIT_ON_FAILURE(MsQuic->SessionOpen(Registration, ALPNs, ARRAYSIZE(ALPNs), nullptr, &Session));
EXIT_ON_FAILURE(MsQuic->ConnectionOpen(Session, ConnectionHandler, nullptr, &Connection));
EXIT_ON_FAILURE(MsQuic->SetParam(Connection, QUIC_PARAM_LEVEL_CONNECTION, QUIC_PARAM_CONN_CERT_VALIDATION_FLAGS, sizeof(CertificateValidationFlags), &CertificateValidationFlags));
EXIT_ON_FAILURE(MsQuic->StreamOpen(Connection, QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL, StreamHandler, nullptr, &Stream));
EXIT_ON_FAILURE(MsQuic->StreamStart(Stream, QUIC_STREAM_START_FLAG_ASYNC));
EXIT_ON_FAILURE(MsQuic->ConnectionStart(Connection, AF_UNSPEC, ServerName, Port));
printf("POST '%s' to %s:%hu\n", FileName, ServerName, Port);
uint64_t TotalBytesSent = 0;
uint64_t TimeStart = QuicTimeUs64();
uint8_t Buffer[IO_SIZE];
QUIC_BUFFER SendBuffer = { 0, Buffer };
SendBuffer.Length = snprintf((char*)Buffer, sizeof(Buffer), POST_HEADER_FORMAT, FileName);
bool EndOfFile = false;
do {
SendBuffer.Length += (uint32_t)
fread(
SendBuffer.Buffer + SendBuffer.Length,
1,
sizeof(Buffer) - SendBuffer.Length,
File);
EndOfFile = SendBuffer.Length != sizeof(Buffer);
EXIT_ON_FAILURE(MsQuic->StreamSend(Stream, &SendBuffer, 1, EndOfFile ? QUIC_SEND_FLAG_FIN : QUIC_SEND_FLAG_NONE, nullptr));
QuicEventWaitForever(SendReady);
TotalBytesSent += SendBuffer.Length;
SendBuffer.Length = 0;
} while (!TransferCanceled && !EndOfFile);
MsQuic->SessionClose(Session);
MsQuic->RegistrationClose(Registration);
MsQuicClose(MsQuic);
uint64_t TimeEnd = QuicTimeUs64();
uint64_t ElapsedUs = QuicTimeDiff64(TimeStart, TimeEnd);
uint64_t SendRateKbps = (TotalBytesSent * 1000 * 8) / ElapsedUs;
printf("%llu bytes sent in %llu.%03llu ms ", TotalBytesSent, ElapsedUs / 1000, ElapsedUs % 1000);
if (SendRateKbps > 1000) {
printf("(%llu.%03llu mbps)\n", SendRateKbps / 1000, SendRateKbps % 1000);
} else {
printf("(%llu kbps)\n", SendRateKbps);
}
QuicEventUninitialize(SendReady);
fclose(File);
QuicPlatformUninitialize();
QuicPlatformSystemUnload();
return 0;
}