C语言HTTPS客户端请求返回HTTP 404,curl请求正常问题排查
C语言HTTPS客户端返回404但curl正常的问题排查
我开发了一个小型C语言HTTPS客户端,用于从公开API获取数据(无需认证)。但尝试获取数据时始终返回HTTP 404错误,尽管确认发送的GET请求资源路径正确——同一机器上用curl以完全相同路径请求可正常返回200。
客户端代码
int main(int argc, char **argv) { char *message=NULL; char *CAfile=NULL; int port=443; char *host_and_port=NULL; unsigned int len=0; SSL *ssl=NULL; BIO *bio=NULL; SSL_CTX *ctx=NULL; int r = 0; ssize_t rr = -1; ssize_t length=0; unsigned char *buffer=NULL; ssize_t l=0; unsigned char *https_stream=NULL; ssize_t https_stream_size=0; CAfile=strdup("mycert.pem"); message=strdup("GET /path1/path2?arg1=val1&arg2=val2 HTTP/1.0\nHost: myhost.com\n\n"); host_and_port=strdup("myhost.com:443"); SSL_load_error_strings(); SSL_library_init(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); if (!(ctx = SSL_CTX_new(TLS_client_method()))) { fprintf(stderr,"The creation of a new SSL_CTX object failed.\n"); fprintf(stderr, "Error: %s\n", ERR_reason_error_string(ERR_get_error())); fprintf(stderr, "%s:", ERR_error_string(ERR_get_error(), NULL)); return -1; } if (!(r = SSL_CTX_load_verify_locations(ctx, CAfile, NULL))) { fprintf(stderr,"Unable to load the trust certificate from file: %s\n",CAfile); fprintf(stderr, "Error: %s\n",ERR_reason_error_string(ERR_get_error())); fprintf(stderr, "%s:",ERR_error_string(ERR_get_error(), NULL)); return -1; } /* Setting up the BIO SSL object */ bio = BIO_new_ssl_connect(ctx); BIO_get_ssl(bio, &ssl); if (!ssl) { fprintf(stderr,"Unable to allocate SSL pointer.\n"); fprintf(stderr, "Error: %s\n", ERR_reason_error_string(ERR_get_error())); fprintf(stderr, "%s:",ERR_error_string(ERR_get_error(), NULL)); return -1; } SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); /* Attempt to connect */ BIO_set_conn_hostname(bio, host_and_port); /* Verify the connection opened and perform the handshake */ if (BIO_do_connect(bio) < 1) { fprintf(stderr,"Unable to connect BIO. %s\n",host_and_port); fprintf(stderr, "Error: %s\n",ERR_reason_error_string(ERR_get_error())); fprintf(stderr, "%s:",ERR_error_string(ERR_get_error(), NULL)); return -1; } length=strlen(message); rr=-1; while (rr < 0) { rr = BIO_write(bio, message, length); if (rr <= 0) { if (!BIO_should_retry(bio)) { fprintf(stderr,"BIO_write should retry.\n"); fprintf(stderr, "Error: %s\n", ERR_reason_error_string(ERR_get_error())); fprintf(stderr, "%s:", ERR_error_string(ERR_get_error(), NULL)); return -1; } } } if (rr!=length) { fprintf(stderr,"Error in sending encripted message.\n"); return -1; } if (buffer) { free(buffer); buffer=NULL; } if (!(https_stream=malloc(sizeof(char)))) { fprintf(stderr, "Allocation memory failed (https_stream string), code=%d (%s)\n",errno, strerror(errno)); return -1; } https_stream[0]=0; https_stream_size=0; length=4096; if (!(buffer=malloc(length*sizeof(char)))) { fprintf(stderr, "Allocation memory failed (buffer string), code=%d (%s)\n",errno, strerror(errno)); return -1; } bzero(buffer,length); rr=-1; while((rr = BIO_read(bio, buffer, length))) { if (rr<0) if (!BIO_should_retry(bio)) { fprintf(stderr,"BIO_read should retry.\n"); fprintf(stderr, "Error: %s\n", ERR_reason_error_string(ERR_get_error())); fprintf(stderr, "%s:", ERR_error_string(ERR_get_error(), NULL)); return -1; } l=https_stream_size; https_stream_size += rr; if (!(https_stream=realloc(https_stream,https_stream_size*sizeof(char)))) { fprintf(stderr, "Re-allocation memory failed (*https_stream string), code=%d (%s)\n",errno, strerror(errno)); return -1; } memcpy(https_stream+l,buffer,rr); bzero(buffer,length); } /* clean up the SSL context resources for the encrypted link */ SSL_CTX_free(ctx); fprintf(stdout,"==========================================\n"); fprintf(stdout,"HTTP Request\n"); fprintf(stdout,"==========================================\n"); fprintf(stdout,"%s\n",message); fprintf(stdout,"==========================================\n"); fprintf(stdout,"\n"); fprintf(stdout,"Host and port: %s\n",host_and_port); fprintf(stdout,"\n"); fprintf(stdout,"HTTP Response\n"); fprintf(stdout,"==========================================\n"); fprintf(stdout,"%s\n",https_stream); fprintf(stdout,"==========================================\n"); if (message) { free(message); message=NULL; } if (buffer) { free(buffer); buffer=NULL; } if (https_stream) { free(https_stream); https_stream=NULL; } if (host_and_port) { free(host_and_port); host_and_port=NULL; } return 0; }
客户端运行输出
========================================== HTTP Request ========================================== GET /path1/path2?arg1=val1&arg2=val2 HTTP/1.0 Host: myhost.com ========================================== Host and port: myhost.com:443 HTTP Response ========================================== HTTP/1.1 404 Not Found Content-Length: 231 x-amz-request-id: tx00000000000000019debd-00636e7b40-39e6dd1-default Accept-Ranges: bytes Content-Type: application/xml Date: Fri, 11 Nov 2022 16:41:36 GMT X-Frame-Options: DENY X-Content-Type-Options: nosniff X-Xss-Protection: 1; mode=block Content-Security-Policy:script-src: https://www.google-analytics.com https://q.quora.com Referrer-Policy: no-referrer-when-downgrade Strict-Transport-Security: max-age=31536000;includeSubDomains;preload <?xml version="1.0" encoding="UTF-8"?><Error><Code>NoSuchBucket</Code><BucketName>myhost.com</BucketName><RequestId>tx00000000000000019debd-00636e7b40-39e6dd1-default</RequestId><HostId>39e6dd1-default-default</HostId></Error> ==========================================
curl请求输出
curl -v --http1.0 "https://myhost.com/path1/path2?arg1=val1&arg2=val2" * Trying <host_IP>:443... * Connected to myhost.com (<host_IP>) port 443 (#0) * ALPN: offers http/1.1 * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH * CAfile: none * CApath: /etc/ssl/certs * TLSv1.2 (OUT), TLS header, Certificate Status (22): * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 * ALPN: server did not agree on a protocol. Uses default. * Server certificate: ... * SSL certificate verify ok. > GET /path1/path2?arg1=val1&arg2=val2 HTTP/1.0 > Host: myhost.com > User-Agent: curl/7.85.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < server: nginx/1.23.2 < date: Fri, 11 Nov 2022 17:40:16 GMT < content-type: application/json; charset=utf-8 < transfer-encoding: chunked < x-powered-by: Express < set-cookie: 3866fdcd36aeaae468ebac902177effe=5f9cbf1450843f212673d67cb86a2f9a; path=/; HttpOnly < cache-control: private < X-Frame-Options: DENY < X-Content-Type-Options: nosniff < X-Xss-Protection: 1; mode=block < Content-Security-Policy:script-src: https://www.google-analytics.com https://q.quora.com < Referrer-Policy: no-referrer-when-downgrade < Strict-Transport-Security: max-age=31536000;includeSubDomains;preload <
问题原因与修复
问题原因
返回的NoSuchBucket错误表明服务器将请求路由到了默认存储桶(myhost.com)而非预期的API服务。核心原因是客户端未设置SNI(Server Name Indication)——现代虚拟主机依赖SNI在TLS握手阶段识别目标域名,curl会自动发送SNI,但代码中缺少这一步,导致服务器无法正确解析要访问的虚拟主机。
修复方法
在获取ssl指针后、调用BIO_do_connect之前,添加SNI设置:
// 在SSL_set_mode之后添加以下代码 SSL_set_tlsext_host_name(ssl, "myhost.com");
修改后的关键代码片段:
BIO_get_ssl(bio, &ssl); if (!ssl) { fprintf(stderr,"Unable to allocate SSL pointer.\n"); fprintf(stderr, "Error: %s\n", ERR_reason_error_string(ERR_get_error())); fprintf(stderr, "%s:",ERR_error_string(ERR_get_error(), NULL)); return -1; } SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); // 添加SNI设置 SSL_set_tlsext_host_name(ssl, "myhost.com"); /* Attempt to connect */ BIO_set_conn_hostname(bio, host_and_port);
验证说明
添加SNI后,TLS握手时会向服务器发送目标域名myhost.com,服务器就能正确路由到对应的API服务,返回预期的200响应。
内容的提问来源于stack exchange,提问作者gda
相关产品推荐
相关产品推荐

