首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带winsock的交互式终端

带winsock的交互式终端
EN

Stack Overflow用户
提问于 2021-10-17 03:01:07
回答 1查看 98关注 0票数 0

我想要创建一个终端,它可以连接不同的计算机通过IP地址在窗口。我使用CreateProcess是因为波芬不能使用

文件main.cpp

代码语言:javascript
复制
#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;
}

文件index.html

代码语言:javascript
复制
<!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>

HTTP方法工作,但POST不起作用。

使用cmd时,在ReadFile上阻塞

当我使用powershell时,服务器只返回它读取的内容。

看上去像这样

我的密码怎么了?

EN

回答 1

Stack Overflow用户

发布于 2022-06-26 04:57:23

大多数网络终端使用WebSocket进行双工连接,如木星、WebSSH.

我使用IO完成端口(IOCP)读取子进程的管道,将文本发送给客户端,两者都是重叠操作。

在我的问题中,管道是由CreatePipe创建的,这是synchronous.In的答案,我使用命名管道作为异步reading.So,我们不必同步等待。

main.cpp

代码语言:javascript
复制
#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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
#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

代码语言:javascript
复制
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

代码语言:javascript
复制
#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

代码语言:javascript
复制
<!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

代码语言:javascript
复制
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();
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69601013

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档