简体   繁体   中英

Winsock send() error when sending POST request to HTTP server

I can't get send() to return anything but -1 when sending the POST string:

#include <vector>
#include <iostream>
#include <string>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32.lib")
using namespace std;

SOCKET socket_create_http()
{
    SOCKET sock;
    SOCKADDR_IN SockAddr;
    struct hostent* host;

    char url[] = "domain.net";

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    host = gethostbyname(url);

    SockAddr.sin_port = htons(54000);
    SockAddr.sin_family = AF_INET;
    SockAddr.sin_addr.s_addr = *((unsigned long*)host->h_addr);

    int bound = bind(sock, (SOCKADDR*)(&SockAddr), sizeof(SockAddr));
            
    return(sock);
}

int main()
{
    WSADATA ws_data;
    WORD ws_version = MAKEWORD(2, 2);
    int startup = WSAStartup(ws_version, &ws_data);

    SOCKET sock_http = socket_create_http();

    string data = "action=0&data=0";

    string request_string = "POST ";
    request_string += "stuff://domain.net/script.php?";
    request_string += " HTTP/1.1\r\n"; // or HTTP/1.0
    request_string += "Host: domain.net\r\n";
    request_string += "Content-Length: " + to_string(data.size()) + "\r\n";
    request_string += "\r\n";
    request_string += data;

    int sent = send(sock_http, request_string.c_str(), strlen(request_string.c_str()), 0);
    cout << "SENT: " << sent << endl;

    closesocket(sock_http);
    return 0;
}

I probably need _WINSOCK_DEPRECATED_NO_WARNINGS in preprocessor definitions to run it.

I've been at this for a few days. Any ideas where I could be going wrong with the socket setup, or post format, or both?


UPDATE: I've updated my code and now I'm receiving 400 bad request when sending the POST string:

#include <vector>
#include <iostream>
#include <string>
#include <sstream>
#include <winsock2.h>
#include <WS2tcpip.h>
#pragma comment (lib, "ws2_32.lib")
using namespace std;

SOCKET sock_http;

SOCKET socket_create_http(const char* hostname, unsigned short port)
{
    hostent* host = gethostbyname(hostname);

    if (host)
    {
        cout << "HTTP HOST: " << host << endl;

        SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (sock != INVALID_SOCKET)
        {
            SOCKADDR_IN SockAddr = {};
            SockAddr.sin_family = AF_INET;
            SockAddr.sin_port = htons(port);
            SockAddr.sin_addr.s_addr = *reinterpret_cast<unsigned long*>(host->h_addr);

            if (connect(sock, reinterpret_cast<SOCKADDR*>(&SockAddr), sizeof(SockAddr)) != SOCKET_ERROR)
            {
                cout << "HTTP SOCKET CREATED: " << sock << endl << endl;
                return sock;
            }
    
            //closesocket(sock); //safety
        }
    }
}
    
bool http_post(string data)
{
    char bb_receive[4096];
    
    ostringstream request; 
    request << "POST stuff://domain.net/script.php HTTP/1.1\r\n"; //stuff is https
    request << "Host: domain.net\r\n";
    request << "Content-Length: " << data.size() << "\r\n";
    request << "Content-Type: application/x-www-form-urlencoded\r\n";
    request << "Connection: close\r\n";
    request << "\r\n";
    request << data;

    string request_string = request.str();

    size_t data_size = request_string.size();
    
    const char* pdata = static_cast<const char*>(request_string.c_str());
    
    bool all_sent = false;
    
    while (data_size > 0)
    {
        int sent = send(sock_http, pdata, data_size, 0);
        if (sent == SOCKET_ERROR)
        {
            cout << "SOCKET FAILED IN SEND LOOP" << endl;
            break;
        }

        pdata += sent;
        data_size -= sent;
    } //SEND
    
    if (data_size <= 0) { all_sent = true; }
        
    if (all_sent)
    {
        ZeroMemory(bb_receive, 4096);
        int bytes_received = recv(sock_http, bb_receive, 4096, 0);
    
        if (bytes_received > 0)
        {
            cout << "Bytes received (Socket " << sock_http << "): " << bytes_received << endl;
        }
    
        for (int i = 0; i < bytes_received; i++) { cout << bb_receive[i]; }
    } //RECEIVE
    
    return all_sent;
}

int main()
{
    WSADATA ws_data;
    WORD ws_version = MAKEWORD(2, 2);

    int startup = WSAStartup(ws_version, &ws_data); if (startup != 0) { cout << "WSA FAILED" << endl; }

    sock_http = socket_create_http("domain.net", 443);

    bool success = http_post("action=0&data=0");

    cout << "WSA Last Error: " << WSAGetLastError() << endl;

    closesocket(sock_http);
    return 0;
}

Here is the output of recv() the server's response:

Bytes received (Socket 532): 4096
HTTP/1.1 400 Bad Request
Date: Mon, 15 Aug 2022 18:23:43 GMT
Server: Apache
Upgrade: h2,h2c
Connection: Upgrade, close
Accept-Ranges: bytes
Vary: Accept-Encoding
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Content-Type: text/html

The main problem with your code is that you are not calling the connect() function to establish a connection to the HTTP server's IP/port. You are using the bind() function instead, which merely establishes the socket's local IP/port, but you can't bind() a socket to a remote IP/Port. That is what connect() is meant for.

When a socket function fails, use WSAGetLastError() to find out why. In your case, send() would be failing with a WSAENOTCONN error:

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send

Return value

If no error occurs, send returns the total number of bytes sent, which can be less than the number requested to be sent in the len parameter. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError .

Error code Meaning
... ...
WSAENOTCONN The socket is not connected.
... ...

That being said, there are many other issues with your code:

  • you are not checking the return values of any of the system functions for failures. socket() can fail. gethostbyname() can fail. And so on. Always check return values before using any output.

  • gethostbyname() has been deprecated for a long time. You really should be using getaddrinfo() instead.

  • what is stuff: ? The first line of the HTTP request should look more like this instead:

    POST /script.php HTTP/1.1\r\n

  • you are missing Content-Type and Connection headers in the HTTP request.

  • you need to call send() in a loop, there is no guarantee it will be able to send the entire request in a single go. Pay attention to send() 's return value, it tells you how many bytes were actually sent. You will have to keep calling send() to re-send unsent bytes until there is nothing left to send.

  • strlen(request_string.c_str()) is redundant, use request_string.size() instead.

  • consider using std::ostringstream instead of using a bunch of std::string concatenations.

Try something more like this:

#include <vector>
#include <iostream>
#include <string>
#include <sstream>
#include <winsock2.h>
#include <WS2tcpip.h>
using namespace std;

#pragma comment (lib, "ws2_32.lib")

SOCKET socket_create_http(const char *hostname, unsigned short port)
{
    hostent* host = gethostbyname(hostname);
    if (host)
    {
        SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (sock != INVALID_SOCKET)
        {
            SOCKADDR_IN SockAddr = {};
            SockAddr.sin_family = AF_INET;
            SockAddr.sin_port = htons(port);
            SockAddr.sin_addr.s_addr = * reinterpret_cast<unsigned long*>(host->h_addr);

            if (connect(sock, reinterpret_cast<SOCKADDR*>(&SockAddr), sizeof(SockAddr)) != SOCKET_ERROR)
                return sock;

            closesocket(sock);
        }
    }

    /* alternatively:

    addrinfo hints = {};
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    addrinfo* addrs;
    
    if (getaddrinfo(hostname, to_string(port).c_str(), &hints, &addrs) == 0)
    {
        for (addrinfo* addr = addrs; addr != NULL; addr = addr->ai_next)
        {
            SOCKET sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
            if (sock != INVALID_SOCKET)
            {
                if (connect(sock, addr->ai_addr, addr->ai_addrlen) != SOCKET_ERROR)
                {
                    freeaddrinfo(addrs);
                    return sock;
                }

                closesocket(sock);
            }
        }

        freeaddrinfo(addrs);
    }

    */
    
    return INVALID_SOCKET;
}
    
bool sendAll(SOCKET sock, const void *data, size_t data_size)
{
    const char *pdata = static_cast<const char *>(data);

    while (data_size > 0)
    {
        int sent = send(sock, pdata, data_size, 0);
        if (sent == SOCKET_ERROR)
            return false;

        pdata += sent;
        data_size -= sent;
    }

    return true;
}

int main()
{
    WSADATA ws_data;
    int startup = WSAStartup(MAKEWORD(2, 2), &ws_data);
    if (startup != 0)
        return 1;

    SOCKET sock_http = socket_create_http("domain.net", 54000);
    if (sock_http == INVALID_SOCKET)
    {
        WSACleanup();
        return 1;
    }

    string data = "action=0&data=0";
            
    ostringstream request;
    request << "POST /script.php HTTP/1.1\r\n"; // or HTTP/1.0
    request << "Host: domain.net:54000\r\n";
    request << "Content-Length: " << data.size() << "\r\n";
    request << "Content-Type: application/x-www-form-urlencoded\r\n";
    request << "Connection: close\r\n";
    request << "\r\n";
    request << data;
    
    string request_string = request.str();
    int sent = sendAll(sock_http, request_string.c_str(), request_string.size());
    cout << "SENT: " << sent << endl;

    closesocket(sock_http);

    WSACleanup();
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM