I want to Create a terminal which can connect different computer by IP address in windows.
I use CreateProcess because _popen cannot use.
file main.cpp
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <cstdlib>
#include <cstdio>
#include <cassert>
#include <csignal>
#include <cstring>
#include <windows.h>
#include <strsafe.h>
#include <fcntl.h>
#include <io.h>
#include <ctime>
#pragma comment (lib, "Ws2_32.lib")
#define DEFAULT_BUFLEN 2048
#define DEFAULT_PORT "7999"
static void ErrorExit(PTSTR lpszFunction)
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf,
0, NULL);
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(1);
}
static const char header[] = "\
HTTP/1.0 200 OK\r\n\
Server: Windows VC++\r\n\
Content-Type: text/html; charset=big-5\r\n\
Access-Control-Allow-Origin: *\r\n\
\r\n";
static int running = true;
void callback(int) {
running = false;
puts("closing");
}
static HANDLE
g_hChildStd_IN_Rd = NULL,
g_hChildStd_IN_Wr = NULL,
g_hChildStd_OUT_Rd = NULL,
g_hChildStd_OUT_Wr = NULL;
#undef TEXT
#define TEXT(a) ((PTSTR)(L##a))
int __cdecl main(void)
{
SECURITY_ATTRIBUTES saAttr = {};
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
ErrorExit(TEXT("StdoutRd CreatePipe"));
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdout SetHandleInformation"));
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
ErrorExit(TEXT("Stdin CreatePipe"));
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
ErrorExit(TEXT("Stdin SetHandleInformation"));
{
PROCESS_INFORMATION piProcInfo = {};
STARTUPINFO siStartInfo = {};
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
char szCmdline[] = "cmd"; // powershell
if (!CreateProcessA( NULL, szCmdline,
NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) {
ErrorExit(TEXT("CreateProcess"));
}
}
signal(SIGINT, callback);
assert(SetConsoleOutputCP(CP_UTF8));
WSADATA wsaData = {};
struct addrinfo* result = NULL;
struct addrinfo hints = {};
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
ErrorExit(TEXT("WSAStartup"));
}
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(NULL, DEFAULT_PORT, &hints, &result) != 0) {
WSACleanup();
ErrorExit(TEXT("getaddrinfo"));
}
SOCKET ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
ErrorExit(TEXT("socket"));
}
if (bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
ErrorExit(TEXT("bind"));
}
freeaddrinfo(result);
if (listen(ListenSocket, 5) == SOCKET_ERROR) {
printf("listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
ErrorExit(TEXT("listen"));
}
puts("Server ready at http://localhost:" DEFAULT_PORT " [running]");
char recvbuf[DEFAULT_BUFLEN] = {};
while (running) {
SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
ErrorExit(TEXT("accept"));
}
{
struct sockaddr_in sockaddr;
int namelen = sizeof(sockaddr);
if (!getpeername(ClientSocket, (struct sockaddr*)&sockaddr, &namelen))
{
time_t timmer = time(NULL);
printf("%s\t#%s", inet_ntoa((in_addr)(*(in_addr*)&sockaddr.sin_addr.S_un.S_addr)), ctime(&timmer));
}
}
ZeroMemory(recvbuf, DEFAULT_BUFLEN);
intptr_t recvlen = recv(ClientSocket, recvbuf, DEFAULT_BUFLEN, 0);
if (recvlen == -1) {
fputs("recv error\n", stderr);
goto SH;
}
// fwrite(recvbuf, iResult, 1, stdout);
if (recvbuf[0] == 'G' && recvbuf[1] == 'E' && recvbuf[2] == 'T') { // HTTP GET method
send(ClientSocket, header, sizeof(header) - 1, 0);
DWORD dwRead;
// success on here
if (ReadFile(g_hChildStd_OUT_Rd, recvbuf, DEFAULT_BUFLEN, &dwRead, NULL)) {
send(ClientSocket, recvbuf, (int)dwRead, 0);
}
}
else {
send(ClientSocket, header, sizeof(header) - 1, 0); // HTTP POST method
char *ptr = strstr(recvbuf, "\r\n\r\n");
if (ptr == NULL || ptr[0] == '\0' || ptr[1] == '\0'||ptr[2]=='\0'|| ptr[3] == '\0'|| ptr[4] == '\0') {
fputs("parse error: POST request\n", stderr);
goto SH;
}
ptr += 4;
{
DWORD dwRead=0, dwWritten=0;
BOOL bSuccess = FALSE;
bSuccess = WriteFile(g_hChildStd_IN_Wr, ptr, (DWORD)((recvlen+recvbuf)-ptr), &dwWritten, NULL);
if (!bSuccess) goto SH;
printf("wrote %lu\n", dwWritten);
// always block on ReadFile
bSuccess = ReadFile(g_hChildStd_OUT_Rd, recvbuf, DEFAULT_BUFLEN, &dwRead, NULL);
printf("read %lu\n", dwRead);
if (!bSuccess || dwRead == 0) goto SH;
send(ClientSocket, ptr, dwRead, 0);
}
}
SH:
if (shutdown(ClientSocket, SD_BOTH) == SOCKET_ERROR) {
puts("shutdown failed");
closesocket(ClientSocket);
WSACleanup();
ErrorExit(TEXT("shutdown"));
}
closesocket(ClientSocket);
}
puts("Exit Server [close]");
WSACleanup();
CloseHandle(g_hChildStd_IN_Wr);
CloseHandle(g_hChildStd_OUT_Rd);
return 0;
}
file index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="big-5">
<title>terminal</title>
</head>
<body>
<style type="text/css">
input {
min-width: 900px;
color: green;
border-width: 0px;
}
span, input, nav {
font-family: Consolas;
font-style: oblique;
}
font{
font-size: 15px;
color: cornflowerblue;
font-family: monospace;
}
</style>
<nav id='version'>waiting for server</nav>
<pre id='shell'>
<input id="new" value="print('Silav gerokê')">
</pre>
<script type="text/javascript">
var _history = [];
var _index = 0;
var shell=document.getElementById('shell');
window.onload=function(){
var x=new XMLHttpRequest();
x.open("GET", "http://127.0.0.1:7999", true);
x.onreadystatechange=function(){
if (x.readyState == 4 && x.status == 200){
document.getElementById('version').innerText=x.response;
document.getElementById('new').focus();
}
}
x.send(null);
}
shell.addEventListener('keydown', function(_){
if(_.keyCode === 13){
_.preventDefault();
var old = document.getElementById('new');
var x=new XMLHttpRequest();
x.open("POST", "http://127.0.0.1:7999", true);
_history.push(old.value);
_index += 1;
x.send(old.value);
x.onreadystatechange=function(){
if (x.readyState == 4 && x.status == 200){
old.removeAttribute('id');
shell.appendChild(document.createElement('br'));
var ne = document.createElement('font');
ne.innerText=x.response;
shell.appendChild(ne);
var input = document.createElement('input');
input.setAttribute('id', 'new');
shell.appendChild(input);
input.focus();
}
}
}else if (_.keyCode==38) {
if (_index){
_index -=1 ;
document.getElementById('new').value=_history[_index];
}
}else if (_.keyCode==40) {
if (_history[_index+1]!=undefined){
_index+=1;
document.getElementById('new').value=_history[_index];
}
}
})
</script>
</body>
</html>
the HTTP GET method works, but POST not.
when I use cmd, block on ReadFile
when I use powershell, server just returns what it reads
it looks like this
What's wrong with my code?
Most Web Terminal use WebSocket for Duplex connection, e.g. Jupyter, WebSSH...
I use IO completion port(IOCP) for reading from child process's pipe, sending text to client, both of them are overlapped operation.
In my question, the pipe was created by CreatePipe, which is synchronous.In my answer, I use named pipe for asynchronous reading.So we don't have to waiting synchronously.
main.cpp
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <mstcpip.h>
#include <crtdbg.h>
#include <wchar.h>
#include <WinInet.h>
#include "Mine.h"
#include "llhttp.h"
#pragma comment(lib, "WS2_32")
#pragma comment(lib, "Mswsock")
#pragma comment(lib, "Wininet")
HANDLE heap, iocp;
#define assert(x) {if (!(x)){printf("error in %s.%d: %s, err=%d\n", __FILE__, __LINE__, #x, WSAGetLastError());}}
#include "types.h"
#include "pipe.cpp"
#include "handshake.cpp"
VOID CloseClient(IOCP* ctx) {
if (ctx->client) {
shutdown(ctx->client, SD_BOTH);
closesocket(ctx->client);
ctx->client = NULL;
ctx->state = State::AfterClose;
#define USEMALLOC 1
#ifdef USEMALLOC
free(ctx);
#else
HeapFree(heap, 0, ctx);
#endif
}
_ASSERT(_CrtCheckMemory());
}
IOCP* initSocket(SOCKET client) {
#ifdef USEMALLOC
IOCP* ctx = (IOCP*)malloc(sizeof(IOCP));
if (ctx == NULL)
return NULL;
ZeroMemory(ctx, sizeof(*ctx));
#else
IOCP* ctx = (IOCP*)HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(IOCP));
if (ctx == NULL)
return NULL;
#endif
ctx->client = client;
HANDLE ret = CreateIoCompletionPort((HANDLE)client, iocp, (ULONG_PTR)ctx, 0);
assert(ret);
_ASSERT(_CrtCheckMemory());
return ctx;
}
#include "frame.cpp"
int http_on_header_field(llhttp_t* parser, const char* at, size_t length) {
Parse_Data* p = (Parse_Data*)parser;
p->length = length;
p->at = at;
return 0;
}
int http_on_header_value(llhttp_t* parser, const char* at, size_t length) {
Parse_Data* p = (Parse_Data*)parser;
p->headers[std::string(p->at, p->length)] = std::string(at, length);
return 0;
}
int http_on_url(llhttp_t* parser, const char* at, size_t length) {
Parse_Data* p = (Parse_Data*)parser;
std::string tmp{ at, length };
DWORD escaped = 1;
char dummy;
HRESULT res = UrlUnescapeA((PSTR)tmp.data(), &dummy, &escaped, 0);
p->uri = (CHAR*)HeapAlloc(heap, 0, escaped + 1);
assert(p->uri);
*(CHAR*)&p->uri[escaped] = '\0';
p->uriLen = escaped;
res = UrlUnescapeA(tmp.data(), (PSTR)p->uri, &escaped, 0);
assert(res == S_OK);
return 0;
}
LPCWSTR encodePath(IOCP* ctx, Parse_Data &parse_data) {
int res = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, parse_data.uri, (int)parse_data.uriLen, (LPWSTR)ctx->buf, sizeof(ctx->buf) / 2);
ctx->state = State::ReadStaticFile;
LPCWSTR file = (LPWSTR)ctx->buf;
if (parse_data.uriLen == 2 && *parse_data.uri == '/') {
file = L"index.html";
}
else {
file += 1;
}
return file;
}
void processRequest(Parse_Data& parse_data, IOCP* ctx, enum llhttp_errno err) {
WSABUF* errBuf = &HTTP_ERR_RESPONCE::internal_server_error;
switch (parse_data.parser.method) {
case llhttp_method::HTTP_GET: {
switch (err) {
case HPE_OK:
{
if (ctx->firstCon) {
ctx->firstCon = false;
auto connection = parse_data.headers.find("Connection");
if (connection != parse_data.headers.end()) {
if (connection->second == "keep-alive" || connection->second == "Keep-Alive") {
ctx->keepalive = true;
DWORD yes = TRUE;
int success = setsockopt(ctx->client, SOL_SOCKET, SO_KEEPALIVE, (char*)&yes, sizeof(yes));
assert(success == 0);
puts("set tcp keep alive(SO_KEEPALIVE)");
auto keepalive = parse_data.headers.find("Keep-Alive");
if (keepalive == parse_data.headers.end())
keepalive = parse_data.headers.find("keep-alive");
if (keepalive != parse_data.headers.end()) {
auto s = keepalive->second.data();
auto timeouts = StrStrA(s, "timeout");
if (timeouts) {
int timeout;
int res = sscanf_s(timeouts + 7, "=%d", &timeout);
if (res > 0) {
printf("set TCP keep alive=%d\n", timeout);
int yes = TRUE;
res = setsockopt(ctx->client, SOL_SOCKET, TCP_KEEPIDLE, (char*)&yes, sizeof yes);
assert(res == 0);
}
else {
puts("Error: failed to parse keepalive seconds...");
}
}
}
}
else {
printf("I got Connection: %s\n", connection->second.data());
}
}
}
auto file = encodePath(ctx, parse_data);
HANDLE hFile = CreateFileW(file,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
errBuf = &HTTP_ERR_RESPONCE::not_found;
goto BAD_REQUEST_AND_RELEASE;
}
HANDLE r = CreateIoCompletionPort(hFile, iocp, (ULONG_PTR)ctx, 0);
assert(r);
const char* mine = getType(file);
ctx->hProcess = hFile;
LARGE_INTEGER fsize{};
BOOL bSuccess = GetFileSizeEx(hFile, &fsize);
assert(bSuccess);
int res = snprintf(ctx->buf, sizeof(ctx->buf),
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %lld\r\n"
"Connection: %s\r\n\r\n", mine, fsize.QuadPart, ctx->keepalive ? "keep-alive" : "close");
assert(res > 0);
ctx->sendBuf->buf = ctx->buf;
ctx->sendBuf->len = (ULONG)res;
WSASend(ctx->client, ctx->sendBuf, 1, NULL, 0, &ctx->sendOL, NULL);
}break;
case HPE_PAUSED_UPGRADE:
{
auto upgrade = parse_data.headers.find("Upgrade");
auto pro = parse_data.headers.find("Sec-WebSocket-Protocol");
if (upgrade != parse_data.headers.end() && pro != parse_data.headers.end()) {
if (upgrade->second == "websocket") {
auto ws_key = parse_data.headers.find("Sec-WebSocket-Key");
if (ws_key != parse_data.headers.end()) {
ctx->state = State::AfterHandShake;
ws_key->second += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
char buf[29];
BOOL ret = HashHanshake(ws_key->second.data(), (ULONG)ws_key->second.length(), buf);
assert(ret);
int len;
len = snprintf(ctx->buf, sizeof(ctx->buf),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: WebSocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Protocol: %s\r\n"
"Sec-WebSocket-Accept: %s\r\n\r\n", pro->second.data(), buf);
assert(len > 0);
ctx->process_name = StrDupA(pro->second.data());
ctx->sendBuf[0].buf = ctx->buf;
ctx->sendBuf[0].len = (ULONG)len;
WSASend(ctx->client, ctx->sendBuf, 1, NULL, 0, &ctx->sendOL, NULL);
}
else {
errBuf = &HTTP_ERR_RESPONCE::bad_request;
goto BAD_REQUEST_AND_RELEASE;
}
}
else {
errBuf = &HTTP_ERR_RESPONCE::bad_request;
goto BAD_REQUEST_AND_RELEASE;
}
}
else {
errBuf = &HTTP_ERR_RESPONCE::bad_request;
goto BAD_REQUEST_AND_RELEASE;
}
}break;
DEFAULT_UNREACHABLE;
}
}break;
case llhttp_method::HTTP_HEAD:
{
if (err == llhttp_errno::HPE_OK) {
auto file = encodePath(ctx, parse_data);
HANDLE hFile = CreateFileW(file,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
constexpr const char* msg = "HTTP/1.1 404 Not Found\r\n\r\n";
ctx->sendBuf[0].buf = (CHAR*)msg;
ctx->sendBuf[0].len = cstrlen(msg);
ctx->state = State::AfterSendHTML;
WSASend(ctx->client, ctx->sendBuf, 1, NULL, 0, &ctx->sendOL, NULL);
return;
}
FILETIME ftWrite;
SYSTEMTIME stUTC;
LARGE_INTEGER fsize;
if (GetFileTime(hFile, NULL, NULL, &ftWrite)) {
if (FileTimeToSystemTime(&ftWrite, &stUTC)) {
if (InternetTimeFromSystemTimeA(&stUTC, INTERNET_RFC1123_FORMAT, ctx->sErr.buf, sizeof(ctx->sErr.buf))) {
if (GetFileSizeEx(hFile, &fsize)) {
int len = snprintf(ctx->buf, sizeof(ctx->buf),
"HTTP/1.1 200 OK\r\n" // no "Accept-Ranges: bytes\r\n" now
"Last-Modified: %s\r\n"
"Conetnt-Length: %lld\r\n"
"\r\n", ctx->sErr.buf, fsize.QuadPart);
ctx->sendBuf[0].buf = ctx->buf;
ctx->sendBuf[0].len = len;
ctx->state = State::AfterSendHTML;
WSASend(ctx->client, ctx->sendBuf, 1, NULL, 0, &ctx->sendOL, NULL);
puts("sended");
return;
}
}
}
}
errBuf = &HTTP_ERR_RESPONCE::internal_server_error;
goto BAD_REQUEST_AND_RELEASE;
}
else {
goto BAD_REQUEST_AND_RELEASE;
}
}break;
default:
{
errBuf = &HTTP_ERR_RESPONCE::method_not_allowed;
BAD_REQUEST_AND_RELEASE:
ctx->state = State::AfterSendHTML;
WSASend(ctx->client, errBuf, 1, NULL, 0, &ctx->sendOL, NULL);
}
}
}
DWORD WINAPI WorkerThread(LPVOID WorkThreadContext) {
DWORD dwbytes = 0;
IOCP* ctx = NULL;
OVERLAPPED* ol = NULL;
for (;;) {
_ASSERT(_CrtCheckMemory());
BOOL ret = GetQueuedCompletionStatus((HANDLE)WorkThreadContext, &dwbytes, (PULONG_PTR)&ctx, &ol, INFINITE);
if (ret == FALSE) {
/*
* GetLastError() retrun thread local error
*/
if (ctx) {
int err=GetLastError();
switch (err) {
case ERROR_BROKEN_PIPE:
{
if (ctx->hProcess != NULL) {
*(PWORD)ctx->buf = htons(1000);
DWORD dwExitCode = 0;
const char* msg;
if (GetExitCodeProcess(ctx->hProcess, &dwExitCode)) {
msg = "process exit with code %u";
}
else {
msg = "process was exited(failed to get exit code)";
// ERROR_INVALID_HANDLE on GetExitCodeProcess
}
int n = snprintf(ctx->buf + 2, sizeof(ctx->buf) - 2, msg, dwExitCode);
assert(n > 0);
websocketWrite(ctx, ctx->buf, n + 2, &ctx->sendOL, ctx->sendBuf, Websocket::Opcode::Close);
CloseHandle(ctx->hProcess);
ctx->hProcess = NULL;
}
}break;
case ERROR_HANDLE_EOF:
{
BOOL res = CloseHandle(ctx->hProcess);
assert(res);
if (ctx->keepalive)
{
ctx->state = State::AfterRecv;
ctx->recvBuf[0].buf = ctx->buf;
ctx->recvBuf[0].len = sizeof(ctx->buf);
ctx->recvOL.Offset = ctx->recvOL.OffsetHigh = 0; // reset reading position
WSARecv(ctx->client, ctx->recvBuf, 1, NULL, &ctx->dwFlags, &ctx->recvOL, NULL);
continue;
}
else {
CloseClient(ctx);
}
}break;
default:
printf("undefined error(GetLastError=%d): I'm going to ignore it\n", err);
}
}
continue;
}
if (ctx == NULL || ol==NULL) {
assert(0);
continue;
}
if (dwbytes == 0) {
CloseClient(ctx);
continue;
}
switch (ctx->state) {
// Accept-Ranges: bytes
case State::AfterRecv:
{
ctx->buf[dwbytes] = '\0';
Parse_Data parse_data{};
enum llhttp_errno err = llhttp_execute(&parse_data.parser, ctx->buf, dwbytes);
if (err != HPE_OK && err != HPE_PAUSED_UPGRADE) {
printf("llhttp_execute error: %s\n", llhttp_errno_name(err));
CloseClient(ctx);
continue;
}
if (parse_data.parser.http_major != 1 || parse_data.parser.http_minor != 1) {
puts("expect HTTP/1.1");
CloseClient(ctx);
continue;
}
printf("%s [%s]\n", llhttp_method_name((llhttp_method)parse_data.parser.method), parse_data.uri);
processRequest(parse_data, ctx, err);
}break; // end case
case State::AfterHandShake:
{
ctx->state = State::WebSocketConnecting;
if (ctx->process_name == NULL) {
CloseClient(ctx);
continue;
}
int size = MultiByteToWideChar(CP_UTF8, 0, ctx->process_name, -1, NULL, 0) * 2;
if (size <= 0) {
assert(0);
CloseClient(ctx);
continue;
}
WCHAR* name = (WCHAR*)HeapAlloc(heap, 0, size+2);
name[size] = L'\0';
size = MultiByteToWideChar(CP_UTF8, 0, ctx->process_name, -1, name, size / 2);
LocalFree(ctx->process_name);
if (size <= 0) {
assert(0);
CloseClient(ctx);
continue;
}
printf("=== spawn process: %ws(%d) ===\n", name, size);
BOOL res = CreateCGI(name, ctx);
HeapFree(heap, 0, name);
if (res == FALSE) {
int n = snprintf(ctx->buf+2, sizeof(ctx->buf)-2, "Error: create process failed, GetLastError()=%d", GetLastError());
*(PWORD)ctx->buf = htons(1000);
assert(n > 0);
websocketWrite(ctx, ctx->buf, n + 2, &ctx->sendOL, ctx->sendBuf, Websocket::Opcode::Close);
continue;
}
ctx->recvBuf[0].buf = ctx->buf;
ctx->recvBuf[0].len = 6;
ctx->dwFlags = MSG_WAITALL;
ctx->Reading6Bytes = true;
WSARecv(ctx->client, ctx->recvBuf, 1, NULL, &ctx->dwFlags, &ctx->recvOL, NULL);
}break;
case State::ReadStaticFile:
{
(void)ReadFile(ctx->hProcess, ctx->buf, sizeof(ctx->buf), NULL, &ctx->recvOL);
ctx->state = State::SendPartFile;
}break;
case State::SendPartFile:
{
ctx->recvOL.Offset += dwbytes;
ctx->sendBuf->len = dwbytes;
WSASend(ctx->client, ctx->sendBuf, 1, NULL, 0, &ctx->sendOL, NULL);
ctx->state = State::ReadStaticFile;
}break;
case State::WebSocketConnecting: {
if (ol == &ctx->recvOL) {
if (ctx->Reading6Bytes) {
onRead6Complete(ctx);
}
else {
onRecvData(ctx);
}
}
else if (ol == &ctx->sOut.ol) {
websocketWrite(ctx, (CHAR*)ctx->sOut.buf, dwbytes, &ctx->sOut.sendOL, ctx->sOut.wsaBuf);
}
else if (ol == &ctx->sErr.ol) {
websocketWrite(ctx, (CHAR*)ctx->sErr.buf, dwbytes, &ctx->sErr.sendOL, ctx->sErr.wsaBuf);
}
else if (ol == &ctx->sOut.sendOL) {
(void)ReadFile(ctx->sOut.pipe, ctx->sOut.buf, sizeof(ctx->sOut.buf), NULL, &ctx->sOut.ol);
}
else if (ol == &ctx->sErr.sendOL) {
(void)ReadFile(ctx->sErr.pipe, ctx->sErr.buf, sizeof(ctx->sErr.buf), NULL, &ctx->sErr.ol);
}
else if (ol == &ctx->sendOL) {
CloseClient(ctx);
}
}break;
case State::AfterSendHTML: {
CloseClient(ctx);
}break;
case State::AfterClose:
{
assert(0);
}break;
default:
{
assert(0);
}
}
}
}
int main()
{
system("chcp 65001");
{
WSADATA wsaData{};
int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
assert(ret == 0);
}
iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
assert(iocp);
heap = GetProcessHeap();
BOOL ret = initHash();
assert(ret);
sockaddr_in ip4{ .sin_family = AF_INET, .sin_port = htons(80) };
SOCKET server = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (server == INVALID_SOCKET)
return 1;
if (bind(server, (struct sockaddr*)&ip4, sizeof(ip4)) == SOCKET_ERROR)
return 1;
if (listen(server, SOMAXCONN) == SOCKET_ERROR)
return 1;
CreateThread(NULL, 0, WorkerThread, iocp, 0, 0);
puts("server listening at http://localhost/ and ws://localhost/");
for (;;) {
sockaddr_in sIn{};
int sInLen = sizeof(sIn);
_ASSERT(_CrtCheckMemory());
SOCKET client = WSAAccept(server, (sockaddr*)&sIn, &sInLen, NULL, NULL);
IOCP* ctx = initSocket(client);
assert(ctx);
ctx->recvBuf[0].buf = ctx->buf;
ctx->recvBuf[0].len = sizeof(ctx->buf);
ctx->firstCon = true;
WSARecv(client, ctx->recvBuf, 1, NULL, &ctx->dwFlags, &ctx->recvOL, NULL);
}
WSACleanup();
closeHash();
_ASSERT(_CrtCheckMemory());
}
types.cpp
namespace Websocket {
using BIT = BYTE;
enum Opcode : BYTE {
Continuation = 0,
Text = 0x1,
Binary = 0x2,
Close = 0x8,
Ping = 0x9,
Pong = 0xA
};
}
template <ULONG N>
consteval ULONG cstrlen(const char(&)[N]) {
return N - 1;
}
consteval ULONG cstrlen(const char* s) {
ULONG res = 0;
for (; *s; s++) {
res++;
}
return res;
}
enum class State : unsigned __int8 {
AfterRecv, ReadStaticFile, SendPartFile, AfterSendHTML, AfterHandShake, WebSocketConnecting, AfterClose
};
int http_on_header_field(llhttp_t* parser, const char* at, size_t length);
int http_on_header_value(llhttp_t* parser, const char* at, size_t length);
int http_on_url(llhttp_t* parser, const char* at, size_t length);
struct Parse_Data {
Parse_Data() : headers{}, uri{}, at{}, length{}, uriLen{} {
llhttp_settings_init(&settings);
settings.on_url = http_on_url;
settings.on_header_field = http_on_header_field;
settings.on_header_value = http_on_header_value;
llhttp_init(&parser, HTTP_REQUEST, &settings);
};
llhttp_t parser;
std::map<std::string, std::string> headers;
const CHAR* uri;
ULONG uriLen;
size_t length;
const char* at;
llhttp_settings_t settings;
};
struct Stdio {
OVERLAPPED ol, sendOL;
HANDLE pipe;
CHAR buf[100];
WSABUF wsaBuf[2];
};
struct IOCP {
SOCKET client;
State state;
OVERLAPPED recvOL, sendOL;
char buf[4096];
DWORD dwFlags;
WSABUF sendBuf[2], recvBuf[1];
bool Reading6Bytes;
unsigned __int64 payload_len;
BYTE header[4];
struct Stdio sIn, sOut, sErr;
HANDLE hProcess;
Websocket::Opcode op;
char* process_name;
bool keepalive, firstCon;
};
namespace HTTP_ERR_RESPONCE {
static char sinternal_server_error[] = "HTTP/1.1 500 Internal Server Error\r\n"
"Connection: close\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n"
"<!DOCTYPE html><html><head><title>500 Internal Server Error</title></head><body><h1 align=\"center\">500 Internal Server Error</h1><hr><p style=\"text-align: center\">The server has some error...<p/></body></html>",
snot_found[] =
"HTTP/1.1 404 Not Found\r\n"
"Connection: close\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n"
"<!DOCTYPE html><html><head><title>404 Not Found</title></head><body><h1 align=\"center\">404 Not Found</h1><hr><p style=\"text-align: center\">The Request File is not found in the server<p/></body></html>",
smethod_not_allowed[] =
"HTTP/1.1 405 Method Not Allowed\r\n"
"Connection: close\r\n"
"Allow: GET, HEAD\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n"
"<!DOCTYPE html><html><head><title>405 Method Not Allowed</title></head><body><h1 align=\"center\">405 Method Not Allowed</h1><hr><p style=\"text-align: center\">You can only use GET, HEAD request<p/></body></html>",
sbad_request[] =
"HTTP/1.1 400 Bad Request\r\n"
"Connection: close\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n"
"<!DOCTYPE html><html><head><title>400 Bad Request</title></head><body><h1 align=\"center\">400 Bad Request</h1><hr><p style=\"text-align: center\">Thes server can't process the request<br>Maybe you miss some header(s)<p/></body></html>";
static WSABUF
internal_server_error = {
cstrlen(sinternal_server_error), sinternal_server_error
},
not_found = {
cstrlen(snot_found), snot_found
},
method_not_allowed = {
cstrlen(smethod_not_allowed), smethod_not_allowed
},
bad_request = {
cstrlen(sbad_request), sbad_request
};
}
pipe.cpp
HANDLE newPipe(WCHAR buf[50], DWORD dwOpenMode) {
HANDLE ret = 0;
static __int64 volatile c = 0;
for (;;) {
swprintf(buf, 50, L"\\\\?\\pipe\\child\\%lld-%lld", InterlockedIncrement64(&c), GetTickCount64());
ret = CreateNamedPipeW(
buf,
dwOpenMode,
PIPE_TYPE_BYTE,
1,
4096,
4096,
5000,
NULL);
if (ret != INVALID_HANDLE_VALUE)
return ret;
int err = GetLastError();
if (err != ERROR_PIPE_BUSY && err != ERROR_ACCESS_DENIED) {
printf("CreateNamedPipeW: %d\n", err);
return INVALID_HANDLE_VALUE;
}
}
}
BOOL CreateCGI(WCHAR* cmd, IOCP* ctx) {
{
STARTUPINFOW si{ .cb = sizeof(si), .dwFlags = STARTF_USESTDHANDLES };
{
WCHAR buf[50];
SECURITY_ATTRIBUTES sa{ .nLength = sizeof(SECURITY_ATTRIBUTES), .bInheritHandle = TRUE };
ctx->sOut.pipe = newPipe(buf, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED);
if (ctx->sOut.pipe == INVALID_HANDLE_VALUE) {
return FALSE;
}
si.hStdOutput = CreateFileW(buf, GENERIC_WRITE, 0, &sa, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (si.hStdOutput == INVALID_HANDLE_VALUE) {
return FALSE;
}
ctx->sErr.pipe = newPipe(buf, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED);
if (ctx->sErr.pipe == INVALID_HANDLE_VALUE) {
return FALSE;
}
si.hStdError = CreateFileW(buf, GENERIC_WRITE, 0, &sa, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (si.hStdError == INVALID_HANDLE_VALUE) {
return FALSE;
}
ctx->sIn.pipe = newPipe(buf, PIPE_ACCESS_OUTBOUND);
if (ctx->sIn.pipe == INVALID_HANDLE_VALUE) { return FALSE; }
si.hStdInput = CreateFileW(buf, GENERIC_READ, 0, &sa, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (si.hStdInput == INVALID_HANDLE_VALUE) {
return FALSE;
}
}
PROCESS_INFORMATION pInfo;
if (CreateProcessW(
NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pInfo)==FALSE){
return FALSE;
}
CloseHandle(pInfo.hThread);
ctx->hProcess = pInfo.hProcess;
CloseHandle(si.hStdError);
CloseHandle(si.hStdInput);
CloseHandle(si.hStdOutput);
}
HANDLE hRet;
hRet = CreateIoCompletionPort(ctx->sOut.pipe, iocp, (ULONG_PTR)ctx, 0);
if (hRet == NULL) {
return FALSE;
}
hRet = CreateIoCompletionPort(ctx->sErr.pipe, iocp, (ULONG_PTR)ctx, 0);
if (hRet == NULL) {
return FALSE;
}
(void)ReadFile(ctx->sOut.pipe, ctx->sOut.buf, sizeof(ctx->sOut.buf), NULL, &ctx->sOut.ol);
(void)ReadFile(ctx->sErr.pipe, ctx->sErr.buf, sizeof(ctx->sErr.buf), NULL, &ctx->sErr.ol);
return TRUE;
}
handshake.cpp
#include <bcrypt.h>
#pragma comment (lib, "Crypt32.lib")
#pragma comment (lib, "bcrypt.lib")
static BCRYPT_ALG_HANDLE hAlgorithm=NULL;
static PBYTE hashO = NULL;
static BCRYPT_HASH_HANDLE hHash = NULL;
BOOL initHash(){
DWORD hashLen, dummy;
// we need hash many times
if ((BCryptOpenAlgorithmProvider( &hAlgorithm, BCRYPT_SHA1_ALGORITHM, NULL, BCRYPT_HASH_REUSABLE_FLAG)) < 0)
return FALSE;
if ((BCryptGetProperty(hAlgorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hashLen, sizeof(hashLen), &dummy, 0)<0))
return FALSE;
hashO = (PBYTE)HeapAlloc(heap, 0, hashLen);
if (NULL == hashO)
{
return FALSE;
}
if ((BCryptCreateHash(
hAlgorithm,
&hHash,
hashO,
hashLen,
NULL,
0,
0))<0)
{
return FALSE;
}
return TRUE;
}
void closeHash(){
if (hAlgorithm)
{
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
}
if (hHash)
{
BCryptDestroyHash(hHash);
}
if (hashO)
{
HeapFree(heap, 0, hashO);
}
}
BOOL HashHanshake(void* key, ULONG length, char *buf/*29 bytes*/){
BYTE sha1_o[20]{};
// SHA-1 20 bytes hash
if ((BCryptHashData(hHash, (PUCHAR)key, length, 0)) < 0)
return FALSE;
if ((BCryptFinishHash(hHash, sha1_o, sizeof(sha1_o), 0)) < 0)
return FALSE;
DWORD size=29;
// base64 encoding
return CryptBinaryToStringA(sha1_o, sizeof(sha1_o), CRYPT_STRING_BASE64|CRYPT_STRING_NOCRLF , buf, &size);
}
frame.cpp
void websocketWrite(IOCP* ctx, const char* msg, ULONG length, OVERLAPPED* ol, WSABUF wsaBuf[2], Websocket::Opcode op = Websocket::Binary) {
ULONG hsize = 2;
ctx->header[0] = 0b10000000 | BYTE(op);
if (length < 126){
ctx->header[1] = (BYTE)length;
}else if (length < 0b10000000000000000){
hsize += 2;
ctx->header[1] = 126;
ctx->header[2] = (BYTE)(length >> 8);
ctx->header[3] = (BYTE)length;
}else{
puts("error: data too long");
return;
}
wsaBuf[0].buf = (char*)ctx->header;
wsaBuf[0].len = hsize;
wsaBuf[1].buf = (char*)msg;
wsaBuf[1].len = length;
WSASend(ctx->client, wsaBuf, 2, NULL, 0, ol, NULL);
_ASSERT(_CrtCheckMemory());
}
void onRecvData(IOCP* ctx) {
PBYTE mask = (PBYTE)ctx->buf;
PBYTE payload = mask + 4;
for (unsigned __int64 i = 0; i < ctx->payload_len; ++i) {
payload[i] = payload[i] ^ mask[i % 4];
}
{
switch (ctx->op) {
case Websocket::Text:
case Websocket::Binary:
{
(void)WriteFile(ctx->sIn.pipe, payload, (DWORD)ctx->payload_len, NULL, &ctx->sIn.ol);
}break;
case Websocket::Close:
{
if (ctx->payload_len >= 2) {
WORD code = ntohs(*(PWORD)payload);
printf("Websocket: closed frame: (code: %u, reason: %.*s)\n", code, (int)(ctx->payload_len-2), payload+2);
websocketWrite(ctx, (const char*)payload, (ULONG)ctx->payload_len, &ctx->sendOL, ctx->sendBuf, Websocket::Close);
}
}return;
case Websocket::Ping:
{
websocketWrite(ctx, (const char*)payload, (ULONG)ctx->payload_len, &ctx->sIn.ol, ctx->sIn.wsaBuf, Websocket::Pong);
}return;
default: {
CloseClient(ctx);
}return;
}
}
ctx->recvBuf[0].len = 6;
ctx->dwFlags = MSG_WAITALL;
ctx->recvBuf[0].buf = ctx->buf;
WSARecv(ctx->client, ctx->recvBuf, 1, NULL, &ctx->dwFlags, &ctx->recvOL, NULL);
ctx->Reading6Bytes = true;
}
void onRead6Complete(IOCP *ctx) {
using namespace Websocket;
PBYTE data = (PBYTE)ctx->buf;
BIT FIN = data[0] & 0b10000000;
if (!FIN) {
puts("FIN MUST be 1");
CloseClient(ctx);
return;
}
ctx->op = Websocket::Opcode(data[0] & 0b00001111);
if (data[0] & 0b01110000) {
puts("RSV is not zero");
CloseClient(ctx);
return;
}
BIT hasmask = data[1] & 0b10000000;
if (!hasmask) {
puts("client MUST mask data");
CloseClient(ctx);
return;
}
ctx->payload_len = data[1] & 0b01111111;
PBYTE precv;
ULONG offset;
switch (ctx->payload_len) {
default:
offset = 0;
data[0] = data[2];
data[1] = data[3];
data[2] = data[4];
data[3] = data[5];
precv = data + 4;
break;
case 126:
ctx->payload_len = ((WORD)(data[2]) << 8) | (WORD)(data[3]);
offset = 2;
data[0] = data[4];
data[1] = data[5];
precv = data + 2;
break;
case 127:
offset = 8;
ctx->payload_len = ntohll(*(unsigned __int64*)&data[2]);
precv = data + 0;
break;
}
if (ctx->payload_len == 0) {
ctx->Reading6Bytes = true;
return;
}
if (ctx->payload_len+6 > sizeof(ctx->buf)) {
puts("Error: data too large!");
CloseClient(ctx);
return;
}
ctx->Reading6Bytes = false;
ctx->recvBuf[0].len = (ULONG)ctx->payload_len + offset;
ctx->recvBuf[0].buf = (char*)precv;
ctx->dwFlags = MSG_WAITALL;
WSARecv(ctx->client, ctx->recvBuf, 1, NULL, &ctx->dwFlags, &ctx->recvOL, NULL);
}
Mine.cpp
#include <map>
#include <string>
std::map<std::wstring, const char*> mineTypeData = {
{L"html", "text/html"},
{L"css", "text/css"},
// skip
};
#include <shlwapi.h>
#pragma comment (lib, "Shlwapi.lib")
const char* getType(const WCHAR* name) {
auto s = PathFindExtensionW(name);
if (s == NULL) {
return "text/plain; charset=utf-8";
}
s++;
auto r = mineTypeData.find(s);
if (r == mineTypeData.end())
return "text/plain; charset=utf-8";
return r->second;
}
index.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Terminal</title>
<link rel="stylesheet" href="https://unpkg.com/xterm#4.18.0/css/xterm.css" />
<script src="https://unpkg.com/xterm#4.18.0/lib/xterm.js"></script>
<script src="https://unpkg.com/xterm-addon-webgl#0.12.0-beta.15/lib/xterm-addon-webgl.js"></script>
<script src="https://unpkg.com/xterm-addon-fit#0.5.0/lib/xterm-addon-fit.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght#600&display=swap" rel="stylesheet">
</head>
<body style="height: 100%;width: 100%;">
<script src="index.js"></script>
</body>
</html>
index.js
var terminal;
this.onerror = alert;
cmd = prompt("process name(e.g. wsl, bash, cmd, powershell)", "cmd");
if (!cmd) {
throw "error: user refuse to enter process name";
}
var ws = new WebSocket('ws://' + location.host, cmd);
ws.onopen = () => {
terminal.write('* * * connection established * * *\r\n');
};
terminal = new Terminal({
fontFamily: "'Source Sans Pro', 'Lucida Console','Source Code Pro', 'monospace'",
cursorBlink: true,
scrollback: 1000,
windowsMode: true,
bellStyle: "sound",
});
terminal.open(document.body);
ws.onclose = e => {
if (e.reason != '') {
terminal.write("\r\n* * * connection closed * * *\r\n"+e.reason);
}
else {
terminal.write('\r\n* * *connection closed...* * *\r\n' + e.code);
}
};
ws.onerror = console.error;
ws.onmessage = (m) => {
m.data.startsWith && terminal.write(m.data);
m.data.text && m.data.text().then((t) => {
terminal.write(t);
});
};
var buf = String();
terminal.onData((e) => {
switch (e) {
case '\u0003':
terminal.write('^C');
terminal.write("\r\n");
ws.send(e);
break;
case '\u0004':
terminal.write('^D');
terminal.write("\r\n");
ws.send(e);
break;
case '\r':
ws.send(buf + '\n');
terminal.write("\r\n");
buf = "";
break;
case '\u007F':
if (terminal._core.buffer.x > 2) {
terminal.write('\b \b');
if (buf.length > 0) {
buf = buf.substr(0, buf.length - 1);
}
}
break;
default:
if (e >= String.fromCharCode(0x20) && e <= String.fromCharCode(0x7E) || e >= '\u00a0') {
buf += e;
terminal.write(e);
}
}
});
const fitAddon = new FitAddon.FitAddon();
terminal.loadAddon(fitAddon);
fitAddon.fit();
terminal.focus();
I have to upload audio chunks continuously on a HLS Server as a part of my project. I am able to upload chunks sucessfully using HTTP Put method with wininet API in C++ using following code.
bool CHTTP::HttpPut(char *szFile,int fileType)
{
bool bErrorFlag = false;
if(m_hInternet == NULL)
{
int retStatus = OpenHTTPSession();
if(retStatus < 1)
{
return true;
}
}
char szPostURL[256];
INTERNET_BUFFERS BufferIn = {0};
DWORD dwBytesRead;
DWORD dwBytesWritten;
BYTE pBuffer[350000];
BOOL bRead, bRet;
static int flag = 1;
BufferIn.dwStructSize = sizeof( INTERNET_BUFFERS );
char szLocalFilePath[256];
if(fileType == AUDIO_CHUNK)
sprintf(szLocalFilePath,"%s/%s",m_strFilePath,szFile);
else
strcpy(szLocalFilePath,szFile);
int iFileSize = 0;
if(fileType == AUDIO_CHUNK)
{
strcpy(szPostURL,m_strPostPath);
strcat(szPostURL,"/");
strcat(szPostURL,szFile);
}
else if(fileType == M3U8)
strcpy(szPostURL,m_szM3U8FileToPost);
else if(fileType == AUTO_M3U8)
strcpy(szPostURL,m_szM3U8AutoPost);
DWORD dwFlags =
INTERNET_FLAG_KEEP_CONNECTION |
INTERNET_FLAG_NO_COOKIES |
INTERNET_FLAG_NO_CACHE_WRITE |
INTERNET_FLAG_NO_UI |
INTERNET_FLAG_RELOAD |INTERNET_FLAG_SECURE;
m_hRequest = HttpOpenRequest(m_hHttpSession, (const char*)"PUT",szPostURL, "HTTP/1.1",NULL , (const char**)"*/*\0",dwFlags, 1);
if(m_hRequest==NULL)
{
bErrorFlag = true;
CloseHTTPSession();
return bErrorFlag;
}
else
{
bErrorFlag = false;
}
int num_of_try = 0;
while(num_of_try < 3)
{
char logDump[1000];
num_of_try++;
HANDLE hFile = CreateFile (szLocalFilePath, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
bErrorFlag = true;
break;
}
else if(!m_bLogFlagCreateErr)
{
m_bLogFlagCreateErr = true;
sprintf(logDump,"CreateFile success %s",szFile);
WriteLog(mLogFile,logDump);
bErrorFlag = false;
}
BufferIn.dwBufferTotal = GetFileSize (hFile, NULL);
iFileSize = BufferIn.dwBufferTotal;
if(!HttpSendRequestEx( m_hRequest, &BufferIn, NULL, HSR_INITIATE, 0))
{
bErrorFlag = true;
m_bLogFlagSend = false;
sprintf(logDump,"Error on HttpSendRequestEx %lu %s",GetLastError(),szFile);
WriteLog(mLogFile,logDump);
break;
}
else
{
bErrorFlag = false;
sprintf(logDump,"HttpSendRequest success %s",szFile);
WriteLog(mLogFile,logDump);
}
DWORD sum = 0;
int size = 0;
do
{
bRead = ReadFile (hFile, pBuffer,iFileSize,
&dwBytesRead, NULL);
if(dwBytesRead != iFileSize)
{
sprintf(logDump,"dwBytesRead %d iFileSize %d %s",dwBytesRead,iFileSize,szFile);
WriteLog(mLogFile,logDump);
}
if(dwBytesRead > 0)
{
bRet=InternetWriteFile( m_hRequest, pBuffer, dwBytesRead,
&dwBytesWritten);
while(dwBytesRead < dwBytesWritten && bRet)
{
sprintf(logDump,"dwBytesRead %d dwBytesWritten %d %s",dwBytesRead,dwBytesWritten,szFile);
WriteLog(mLogFile,logDump);
bRet=InternetWriteFile( m_hRequest, pBuffer+dwBytesWritten, dwBytesRead - dwBytesWritten ,&dwBytesWritten);
}
if(!bRet)
{
int error = GetLastError();
sprintf(logDump,"InternetWriteFile %lu %s",error,szFile);
WriteLog(mLogFile,logDump);
bErrorFlag = true;
break;
}
else
{
sprintf(logDump,"InternetWriteFile buffer success %s",szFile);
WriteLog(mLogFile,logDump);
bErrorFlag = false;
}
}
}
while (dwBytesRead == iFileSize);
CloseHandle (hFile);
if(!HttpEndRequest(m_hRequest, NULL, 0, 0))
{
int error = GetLastError();
if(error != 12032)
{
sprintf(logDump,"HttpEndRequest %lu %s",error,szFile);
WriteLog(mLogFile,logDump)
bErrorFlag = true;
break;
}
else
{
bErrorFlag = true;
continue;
}
}
else
{
sprintf(logDump,"HttpEndRequest success %s",szFile);
WriteLog(mLogFile,logDump);
DWORD dwCode, dwCodeSize;
dwCodeSize = sizeof(DWORD);
if(!HttpQueryInfo(m_hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &dwCode, &dwCodeSize, NULL))
{
int error = GetLastError();
char tmp[256];
sprintf(tmp,"HttpQueryfails %d %s",error, szFile);
WriteLog(mLogFile,tmp);
}
else
{
if(dwCode != 201 && dwCode != 204)
{
char tmp[256];
sprintf(tmp,"dwcode %d is not 201 or 204 %s",dwCode,szFile);
WriteLog(mLogFile,tmp);
bErrorFlag = true;
break;
}
}
bErrorFlag = false;
break;
}
}
CloseHTTPSession();
return bErrorFlag;
}
Most of the times chunks uploaded are of full size. Randomly chunks uploaded on server are not of full size as shown in following image.
sample image
In such case I am not getting any error messages, even dwBytesWritten returned by InternetWriteFile function is also correct. I am unable to understand what should I do to solve it. Any help in this regard is appreciated.
The short version:
I have written a small program to print a report. This program is exec'ed from our proprietary server, that is running as a windows service. It works in our development environment, but not on our customer's network.
When I attempt to access the printing system in the failing case, I get a "No default printer" error. We had the customer create a new login account that has a default printer defined, and restarted the server using that login account. Same error.
Note that this error is generated when trying to find a specified printer, not when we try to print to it.
Is there any way to convince a server-spawned process that printers do, in fact, exist?
The long version:
In our "current" production environment, we have the following:
1. a proprietary server that runs as a service under windows.
2. a desktop client
--> accesses data via that service
--> uses fastreport4 to generate reports
--> developed using C++Builder6 (and VCL)
3. a PocketPC-based application that runs on scanning devices
--> uses Apache to communicate with the service
--> also uses Apache to poke a cgi-bin application that will bring up
the desktop app in stealth mode, run a report, and print it.
I have been tasked with re-implementing the Pocket-PC functionality on an Android-based scanning device, and removing the Apache layer from the architecture. In the Android app, I've written a communication layer to access the server (service) directly (the same way the desktop does). The server has the ability to exec applications, so I've written a small one to just gather data, and call fastreport to format and print it.
Works great. No problem. ... in our development environment. It works in our office network with the server running on a Windows 7 system. It works when run from the command line on our customer's Windows 2008 server. It does not work when run from the service on the customer's server.
So here's the code. In my current revision, I have try/catch and debug print statements around (nearly) every line of code. I removed them for readability.
bool __fastcall TFormReportRunner::mySetPrinter(const char* name)
{
char pDevice[MAX_PATH];
char pDriver[MAX_PATH];
char APort[100];
UINT ADeviceMode;
bool printerFound = false;
bool errorFound = false;
String PrinterPort = String(name).UpperCase();
TPrinter* Prntr;
// I added this bit to see if it helps. Seems to make no difference
bool rc = SetDefaultPrinter("");
Prntr = Printer();
if (Prntr == NULL)
{
LogErrorMsg("Printer() returned null.");
return false;
}
int i = Prntr->Printers->Count - 1;
for (; i >= 0; i--)
{
// In the failing case, this next statement is the one that causes an exception.
Prntr->PrinterIndex = i;
Prntr->GetPrinter(pDevice, pDriver, APort, ADeviceMode);
DWORD SizeNeeded = 0;
HANDLE PrinterHandle;
if (OpenPrinter(pDevice, &PrinterHandle, NULL) == 0)
{
LogErrorMsg("Could not open printer");
return false;
}
GetPrinter(PrinterHandle, 2, NULL, 0, &SizeNeeded);
if (SizeNeeded == 0)
{
ClosePrinter(PrinterHandle);
LogErrorMsg("Could not retrieve printer info size");
return false;
}
PRINTER_INFO_2 PrinterInfo2;
char* buffer = new char[SizeNeeded];
if (GetPrinter(PrinterHandle, 2, buffer, SizeNeeded, &SizeNeeded) == 0)
{
ClosePrinter(PrinterHandle);
delete [] buffer;
LogErrorMsg("Could not retrieve printer info");
return false;
}
String PortName = ((PRINTER_INFO_2*)buffer)->pPortName;
delete [] buffer;
ClosePrinter(PrinterHandle);
if (PrinterPort == PortName)
{
frxReport1->PrintOptions->Printer = pDevice;
break;
}
}
Prntr->PrinterIndex = i;
return true;
}
One of the customer's IT guys says that in order to have the Apache version work they have to run Apache as administrator with a defined default printer AND have that admin account logged in for printing to work. He suspects that if we run our service in the same configuration it will start to work. I have not been able to replicate that on our network. My admin account always works, whether anyone is currently logged into the system, or not. But this is Windows 7/Professional, and not a server version.
This has got to be possible... Apache's doing it. If it can find a printer and print to it, I should be able to, right?
Any hints or help will be greatly appreciated. Anything. Really. :)
Thanks,
-Karen
Edit: Adding server-side code.
A couple of notes, first. One, this was written 20-ish years ago (by someone else). Two, it is NOT a VCL app (and was compiled with the Microsoft compiler). Three, we will not change it. Recompiling with current compilers is WAY too risky for something that is otherwise working.
int myServer::RunProcess2(ClientCall *call, vector<string> &args, vector<string> &env, const char* input, unsigned int insize,
string *output, string *error)
{
CancelProcess2(); // only one process allowed per connection
string cmdline;
for (unsigned int i = 0; i < args.size(); i++)
{
if (i != 0)
cmdline += ' ';
cmdline += args[i];
}
env.push_back(EnvPATH);
int size = 1;
for (unsigned int i = 0; i < env.size(); i++)
{
size += env[i].size() + 1;
}
char *environment = (char*)malloc(size);
if (environment == NULL)
{
call->error = "Could not allocate memory for process environment variables";
return 0;
}
char *ptr = environment;
for (unsigned int i = 0; i < env.size(); i++)
{
size = env[i].size() + 1;
memcpy(ptr, env[i].c_str(), size);
ptr += size;
}
ptr[0] = '\0';
HANDLE hInputReadPipe = NULL, hInputWritePipe = NULL;
HANDLE hReadPipe, hWritePipe;
HANDLE hErrorReadPipe, hErrorWritePipe;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = true;
// create output pipe
if (CreatePipe(&hReadPipe, &hWritePipe, &sa, 4096) == 0)
{
free(environment);
call->error = "Error creation Pipe";
return 0;
}
// create error pipe
if (CreatePipe(&hErrorReadPipe, &hErrorWritePipe, &sa, 4096) == 0)
{
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
free(environment);
call->error = "Error creating Pipe";
return 0;
}
if (insize > 0)
{
// create input pipe
if (CreatePipe(&hInputReadPipe, &hInputWritePipe, &sa, 4096) == 0)
{
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
CloseHandle(hErrorReadPipe);
CloseHandle(hErrorWritePipe);
free(environment);
call->error = "Error creating Pipe";
return 0;
}
}
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdOutput = hWritePipe;
si.hStdError = hErrorWritePipe;
si.hStdInput = hInputReadPipe;
PROCESS_INFORMATION pi;
if (CreateProcess(NULL, (char*)cmdline.c_str(), NULL, NULL, true, 0, environment, NULL, &si, &pi) == 0)
{
CloseHandle(hErrorReadPipe);
CloseHandle(hErrorWritePipe);
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
if (hInputReadPipe != NULL)
{
CloseHandle(hInputReadPipe);
CloseHandle(hInputWritePipe);
}
free(environment);
call->error = string("Error executing command: ") + cmdline + "\n" + GetErrorText();
return 0;
}
report_handle = pi.hProcess;
CloseHandle(hErrorWritePipe);
CloseHandle(hWritePipe);
if (hErrorReadPipe != NULL)
CloseHandle(hInputReadPipe);
char buffer[4097];
DWORD BytesAvail;
DWORD BytesRead = 0;
DWORD BytesWritten = 0;
DWORD BytesToRead = sizeof(buffer) - 1;
DWORD BytesToWrite = insize;
bool finished_readpipe = false, finished_errorpipe = false;
bool finished_inputpipe = (insize == 0);
int wait_time = 1;
bool error_occurred = false;
while (finished_readpipe == false || finished_errorpipe == false || finished_inputpipe == false)
{
if (finished_inputpipe == false)
{
if (BytesToWrite <= 0)
{
CloseHandle(hInputWritePipe);
hInputWritePipe = NULL;
finished_inputpipe = true;
continue;
}
BytesAvail = 1000;
/*if (PeekNamedPipe(hInputWritePipe, NULL, NULL, NULL, &BytesAvail, NULL) == 0)
{
DWORD temp = GetLastError();
// pipe has been closed
finished_inputpipe = true;
continue;
}*/
if (BytesAvail > 0)
{
if (BytesAvail > BytesToWrite)
BytesAvail = BytesToWrite;
if (WriteFile(hInputWritePipe, input, BytesAvail, &BytesWritten, NULL) == 0)
{
if (GetLastError() == ERROR_NO_DATA)
{
int a = 2; // Pipe was closed (normal exit path).
}
finished_inputpipe = true;
continue;
}
input += BytesWritten;
BytesToWrite -= BytesWritten;
if (BytesToWrite == 0)
{
finished_inputpipe = true;
}
continue;
}
}
if (finished_readpipe == false)
{
while (true)
{
if (PeekNamedPipe(hReadPipe, NULL, NULL, NULL, &BytesAvail, NULL) == 0)
{
// pipe has been closed
finished_readpipe = true;
break;
}
if (BytesAvail <= 0)
break;
if (BytesAvail > sizeof(buffer) - 1)
BytesAvail = sizeof(buffer) - 1;
if (ReadFile(hReadPipe, buffer, BytesAvail, &BytesRead, NULL) == 0)
{
finished_readpipe = true;
break;
}
if (BytesRead == 0)
{
finished_readpipe = true;
break;
}
buffer[BytesRead] = '\0';
*output += buffer;
if (output->length() >= MAX_PROCESS_OUTPUT)
{
finished_inputpipe = true;
finished_readpipe = true;
finished_errorpipe = true;
error_occurred = true;
call->error = "Output limit reached";
}
}
if (finished_readpipe == true)
continue;
}
if (finished_errorpipe == false)
{
while (true)
{
if (PeekNamedPipe(hErrorReadPipe, NULL, NULL, NULL, &BytesAvail, NULL) == 0)
{
// pipe has been closed
finished_errorpipe = true;
break;
}
if (BytesAvail <= 0)
break;
if (BytesAvail > sizeof(buffer) - 1)
BytesAvail = sizeof(buffer) - 1;
if (ReadFile(hErrorReadPipe, buffer, BytesAvail, &BytesRead, NULL) == 0)
{
finished_errorpipe = true;
break;
}
if (BytesRead == 0)
{
finished_errorpipe = true;
break;
}
buffer[BytesRead] = '\0';
*error += buffer;
if (error->length() >= MAX_PROCESS_OUTPUT)
{
finished_inputpipe = true;
finished_readpipe = true;
finished_errorpipe = true;
error_occurred = true;
call->error = "Error output limit reached";
}
}
if (finished_errorpipe == true)
continue;
}
// don't tie up the server
if (wait_time < 100)
wait_time++;
Sleep(wait_time);
}
if (error_occurred == false)
WaitForSingleObject(pi.hProcess, INFINITE);
process_mutex.lock();
report_handle = NULL;
process_mutex.unlock();
DWORD exit_code = 0;
GetExitCodeProcess(pi.hProcess, &exit_code);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(hReadPipe);
CloseHandle(hErrorReadPipe);
if (hInputWritePipe != NULL)
CloseHandle(hInputWritePipe);
free(environment);
return exit_code;
}