Win32 Socket编程出现奇怪的400 Bad Request错误求助
解决Win32 Socket发送明文HTTP到HTTPS端口的400错误
问题原因
你连接的是443端口(标准HTTPS端口),但直接向该端口发送了明文HTTP请求。HTTPS协议要求在发送HTTP数据前,必须先通过TLS/SSL握手建立加密连接,否则服务器会拒绝明文请求,返回HTTP/1.1 400 Bad Request及the plain HTTP request was sent to HTTPS port错误。
对比你的curl命令:
curl -v https://reqres.in/api/users:443:curl识别https://前缀后,会自动完成TLS握手,再加密发送HTTP请求,因此能正常获取数据。curl -v reqres.in/api/users:443:curl默认使用HTTP协议,直接发送明文请求到443端口,和你的代码行为一致,因此触发相同错误。
解决方案
要解决这个问题,你需要在建立TCP连接后,通过Schannel API完成TLS握手,建立加密通道,再通过加密通道发送HTTP请求。以下是修改后的完整代码(恢复了必要的错误检查,添加了TLS处理逻辑):
#include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <schannel.h> #include <secur32.h> #pragma comment(lib, "Ws2_32.lib") #pragma comment(lib, "secur32.lib") #define DEFAULT_BUFLEN 1024 #define DEFAULT_PORT "443" int main() { WSADATA wsaData; SOCKET ConnectSocket = INVALID_SOCKET; struct addrinfo* result = NULL, * ptr = NULL, hints; const char* sendbuf = "GET /api/users HTTP/1.1\r\nHost: reqres.in\r\nConnection: close\r\n\r\n"; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN; // 初始化Winsock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; // 解析服务器地址和端口 iResult = getaddrinfo("reqres.in", DEFAULT_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } // 尝试连接到服务器 for (ptr = result; ptr != NULL; ptr = ptr->ai_next) { ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); if (ConnectSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); ConnectSocket = INVALID_SOCKET; continue; } break; } freeaddrinfo(result); if (ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } // -------------------------- 初始化TLS上下文 -------------------------- CtxtHandle hContext = { 0 }; SECURITY_STATUS secStatus; SecPkgContext_StreamSizes streamSizes = { 0 }; SecBufferDesc sendBufferDesc, recvBufferDesc; SecBuffer sendBuffer, recvBuffer; char tlsSendBuf[DEFAULT_BUFLEN], tlsRecvBuf[DEFAULT_BUFLEN]; // 初始化安全上下文 SECURITY_ATTRIBUTES secAttr = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE }; secStatus = InitializeSecurityContext(NULL, NULL, (LPWSTR)L"reqres.in", ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY, 0, SECURITY_NATIVE_DREP, NULL, 0, &hContext, &sendBufferDesc, NULL, NULL); if (secStatus != SEC_E_OK && secStatus != SEC_I_CONTINUE_NEEDED) { printf("InitializeSecurityContext failed: 0x%08x\n", secStatus); closesocket(ConnectSocket); WSACleanup(); return 1; } // 发送TLS握手数据 if (secStatus == SEC_I_CONTINUE_NEEDED) { sendBufferDesc.ulVersion = SECBUFFER_VERSION; sendBufferDesc.cBuffers = 1; sendBufferDesc.pBuffers = &sendBuffer; sendBuffer.BufferType = SECBUFFER_TOKEN; sendBuffer.pvBuffer = tlsSendBuf; sendBuffer.cbBuffer = sizeof(tlsSendBuf); do { secStatus = InitializeSecurityContext(NULL, &hContext, (LPWSTR)L"reqres.in", ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY, 0, SECURITY_NATIVE_DREP, &recvBufferDesc, 0, &hContext, &sendBufferDesc, NULL, NULL); if (sendBufferDesc.cBuffers > 0 && sendBufferDesc.pBuffers[0].cbBuffer > 0) { iResult = send(ConnectSocket, (char*)sendBufferDesc.pBuffers[0].pvBuffer, sendBufferDesc.pBuffers[0].cbBuffer, 0); if (iResult == SOCKET_ERROR) { printf("send TLS data failed: %d\n", WSAGetLastError()); DeleteSecurityContext(&hContext); closesocket(ConnectSocket); WSACleanup(); return 1; } } if (secStatus == SEC_I_CONTINUE_NEEDED) { // 接收服务器的TLS响应 recvBufferDesc.ulVersion = SECBUFFER_VERSION; recvBufferDesc.cBuffers = 1; recvBufferDesc.pBuffers = &recvBuffer; recvBuffer.BufferType = SECBUFFER_TOKEN; recvBuffer.pvBuffer = tlsRecvBuf; recvBuffer.cbBuffer = sizeof(tlsRecvBuf); iResult = recv(ConnectSocket, tlsRecvBuf, sizeof(tlsRecvBuf), 0); if (iResult <= 0) { printf("recv TLS data failed: %d\n", WSAGetLastError()); DeleteSecurityContext(&hContext); closesocket(ConnectSocket); WSACleanup(); return 1; } recvBuffer.cbBuffer = iResult; } } while (secStatus == SEC_I_CONTINUE_NEEDED); } if (secStatus != SEC_E_OK) { printf("TLS handshake failed: 0x%08x\n", secStatus); DeleteSecurityContext(&hContext); closesocket(ConnectSocket); WSACleanup(); return 1; } // 获取流大小信息,用于加密/解密数据 secStatus = QueryContextAttributes(&hContext, SECPKG_ATTR_STREAM_SIZES, &streamSizes); if (secStatus != SEC_E_OK) { printf("QueryContextAttributes failed: 0x%08x\n", secStatus); DeleteSecurityContext(&hContext); closesocket(ConnectSocket); WSACleanup(); return 1; } // -------------------------- 加密并发送HTTP请求 -------------------------- char encryptedBuf[DEFAULT_BUFLEN + streamSizes.cbHeader + streamSizes.cbTrailer]; SecBuffer sendBuffers[4]; SecBufferDesc sendDesc; sendDesc.ulVersion = SECBUFFER_VERSION; sendDesc.cBuffers = 3; sendDesc.pBuffers = sendBuffers; sendBuffers[0].BufferType = SECBUFFER_STREAM_HEADER; sendBuffers[0].pvBuffer = encryptedBuf; sendBuffers[0].cbBuffer = streamSizes.cbHeader; sendBuffers[1].BufferType = SECBUFFER_DATA; sendBuffers[1].pvBuffer = (char*)sendbuf; sendBuffers[1].cbBuffer = strlen(sendbuf); sendBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER; sendBuffers[2].pvBuffer = encryptedBuf + streamSizes.cbHeader + sendBuffers[1].cbBuffer; sendBuffers[2].cbBuffer = streamSizes.cbTrailer; secStatus = EncryptMessage(&hContext, 0, &sendDesc, 0); if (secStatus != SEC_E_OK) { printf("EncryptMessage failed: 0x%08x\n", secStatus); DeleteSecurityContext(&hContext); closesocket(ConnectSocket); WSACleanup(); return 1; } // 发送加密后的HTTP请求 iResult = send(ConnectSocket, encryptedBuf, streamSizes.cbHeader + strlen(sendbuf) + streamSizes.cbTrailer, 0); if (iResult == SOCKET_ERROR) { printf("send encrypted HTTP data failed: %d\n", WSAGetLastError()); DeleteSecurityContext(&hContext); closesocket(ConnectSocket); WSACleanup(); return 1; } // -------------------------- 接收并解密服务器响应 -------------------------- char decryptedBuf[DEFAULT_BUFLEN]; SecBuffer recvBuffers[4]; SecBufferDesc recvDesc; int totalDecrypted = 0; recvDesc.ulVersion = SECBUFFER_VERSION; recvDesc.cBuffers = 4; recvDesc.pBuffers = recvBuffers; recvBuffers[0].BufferType = SECBUFFER_STREAM_HEADER; recvBuffers[0].pvBuffer = tlsRecvBuf; recvBuffers[0].cbBuffer = streamSizes.cbHeader; recvBuffers[1].BufferType = SECBUFFER_DATA; recvBuffers[1].pvBuffer = tlsRecvBuf + streamSizes.cbHeader; recvBuffers[1].cbBuffer = DEFAULT_BUFLEN - streamSizes.cbHeader - streamSizes.cbTrailer; recvBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER; recvBuffers[2].pvBuffer = tlsRecvBuf + streamSizes.cbHeader + recvBuffers[1].cbBuffer; recvBuffers[2].cbBuffer = streamSizes.cbTrailer; recvBuffers[3].BufferType = SECBUFFER_EMPTY; recvBuffers[3].pvBuffer = NULL; recvBuffers[3].cbBuffer = 0; printf("Received response:\n"); do { iResult = recv(ConnectSocket, tlsRecvBuf, sizeof(tlsRecvBuf), 0); if (iResult <= 0) { if (iResult < 0) { printf("recv failed with error: %d\n", WSAGetLastError()); } break; } recvBuffers[1].cbBuffer = iResult - streamSizes.cbHeader - streamSizes.cbTrailer; secStatus = DecryptMessage(&hContext, &recvDesc, 0, NULL); if (secStatus != SEC_E_OK) { printf("DecryptMessage failed: 0x%08x\n", secStatus); break; } // 输出解密后的响应内容 memcpy(decryptedBuf + totalDecrypted, recvBuffers[1].pvBuffer, recvBuffers[1].cbBuffer); totalDecrypted += recvBuffers[1].cbBuffer; printf("%.*s", recvBuffers[1].cbBuffer, (char*)recvBuffers[1].pvBuffer); } while (iResult > 0); // 清理资源 DeleteSecurityContext(&hContext); closesocket(ConnectSocket); WSACleanup(); return 0; }
关键修改说明
- 添加Schannel依赖:包含
<schannel.h>和<secur32.h>头文件,链接secur32.lib库,用于TLS握手和数据加密/解密。 - TLS握手流程:通过
InitializeSecurityContext完成TLS握手,与服务器交换加密密钥,建立安全通道。 - 加密发送HTTP请求:使用
EncryptMessage对明文HTTP请求加密后,再通过Socket发送。 - 解密响应数据:接收服务器的加密响应后,用
DecryptMessage解密,得到明文HTTP响应内容。
内容的提问来源于stack exchange,提问作者Marco Vinicio
相关产品推荐
相关产品推荐

