为何仅Firefox能显示自制HTTP服务器响应?Chrome/Chromium无内容
Chrome/Chromium无法显示HTTP服务器响应,Firefox却正常的原因
测试背景
为理清HTTP与TCP的关系,我编写了一个监听特定地址+端口的TCP套接字服务器,通过浏览器访问测试后发现:
- 服务器
accept()的套接字收到的是符合HTTP规范的文本内容 - 推测要让浏览器显示内容,服务器需向套接字写入符合HTTP规范的响应
- 基于最小化HTTP响应示例编写服务器代码后,出现兼容性问题:Firefox能正常显示"Hello World!",但Chrome和Chromium页面空白
浏览器请求内容
Chrome发送的请求
GET / HTTP/1.1 Host: 127.0.0.23:9018 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9
Firefox发送的请求
GET / HTTP/1.1 Host: 127.0.0.23:9018 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Connection: keep-alive Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1
服务器代码
#include <arpa/inet.h> #include <cerrno> #include <cstdio> #include <cstring> #include <fcntl.h> #include <iostream> #include <netinet/in.h> #include <pthread.h> #include <semaphore.h> #include <stdexcept> #include <sstream> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #define IP "127.0.0.23" #define PORT (9018) /** * A primitive, POC-level HTTP server that accepts its first incoming connection * and sends back a minimal HTTP OK response. */ class Server { private: static const std::string ip_; static const std::uint16_t port_{PORT}; int listen_sock_; pthread_t tid_; public: /** * Ctor: create and bind listen_sock_ and start a thread for startRoutine(). */ Server() { using namespace std; int result; if (! createSocket()) { throw runtime_error("failed creating socket"); } if (! bindSocket()) { throw runtime_error("failed binding socket"); } if ((result = pthread_create(&tid_, NULL, startRoutine, this))) { std::stringstream ss; ss << "pthread_create() error " << errno << "(" << result << ")"; std::cerr << ss.str() << std::endl; throw runtime_error("failed spawning Server thread"); } } /** Dtor: wait for the spawned thread and destroy listen_sock_. */ ~Server() { pthread_join( tid_, NULL ); destroySocket(); } private: /** Creates listen_sock_ as a stream socket. */ bool createSocket() { listen_sock_ = socket(PF_INET, SOCK_STREAM, 0); if (listen_sock_ < 0) { std::stringstream ss; ss << "socket() error " << errno << "(" << strerror(errno) << ")"; std::cerr << ss.str() << std::endl; } return (listen_sock_ >= 0); } /** Shuts down and closes listen_sock_. */ void destroySocket() { if (listen_sock_ >= 0) { shutdown(listen_sock_, SHUT_RDWR); close(listen_sock_); } } /** Binds listen_sock_ to ip_ and port_. */ bool bindSocket() { int ret; sockaddr_in me; me.sin_family = PF_INET; me.sin_port = htons(port_); me.sin_addr.s_addr = inet_addr(ip_.c_str()); int optval = 1; setsockopt(listen_sock_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval); if ((ret = bind(listen_sock_, (sockaddr*)&me, sizeof me))) { std::stringstream ss; ss << "bind() error " << errno << "(" << strerror(errno) << ")"; std::cerr << ss.str() << std::endl; } return (! ret); } /** * Accept a connection from listen_sock_. * Caller guarantees listen_sock_ has been listen()ed to already. * @param tv [in, out] How long to wait to accept a connection. * @return accepted socket; -1 on any error. */ int acceptConnection(timeval& tv) { int sock = -1; int ret; fd_set readfds; sockaddr_in peer; socklen_t addrlen = sizeof peer; FD_ZERO(&readfds); FD_SET(listen_sock_, &readfds); ret = select(listen_sock_ + 1, &readfds, NULL, NULL, &tv); if (ret < 0) { std::stringstream ss; ss << "select() error " << errno << "(" << strerror(errno) << ")"; std::cerr << ss.str() << std::endl; return sock; } else if (! ret) { std::cout << "no connections within " << tv.tv_sec << "seconds" << std::endl; return sock; } if ((sock = accept(listen_sock_, (sockaddr*)&peer, &addrlen)) < 0) { std::stringstream ss; ss << "accept() error " << errno << "(" << strerror(errno) << ")"; std::cerr << ss.str() << std::endl; } else { std::stringstream ss; ss << "socket " << sock << " accepted connection from " << inet_ntoa( peer.sin_addr ) << ":" << ntohs(peer.sin_port); std::cout << ss.str() << std::endl; } return sock; } /** Read from the specified socket and dump to stdout. */ static void dumpReceivedContent(const int& sock) { fd_set readfds; struct timeval tv = {30, 0}; int ret; FD_ZERO(&readfds); FD_SET(sock, &readfds); ret = select(sock + 1, &readfds, NULL, NULL, &tv); if (ret < 0) { std::stringstream ss; ss << "select() error " << errno << "(" << strerror(errno) << ")"; std::cerr << ss.str() << std::endl; return; } else if (! ret) { std::cout << "no content received within " << tv.tv_sec << "seconds" << std::endl; return; } if (FD_ISSET(sock, &readfds)) { ssize_t bytes_read; char buf[80] = {0}; fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); std::cout << "received content:" << std::endl; std::cout << "----" << std::endl; while ((bytes_read = read(sock, buf, (sizeof buf) - 1)) >= 0) { buf[bytes_read] = '\0'; std::cout << buf; } std::cout << std::endl << "----" << std::endl; } } /** Write a minimal HTTP OK response to the specified socker. */ static void sendMinHttpResponse(const int& sock) { static const std::string resp = "HTTP/1.1 200 OK\r\n" "Content-Length: 13\r\n" "Content-Type: text/plain\r\n\r\nHello World!"; write(sock, resp.c_str(), resp.length()); } /** * Thread start routine: listen for, then accept connections; dump received * content; send a minimal response. */ static void* startRoutine(void* arg) { Server* s; if (! (s = (Server*)arg)) { std::cout << "Bad arg" << std::endl; return NULL; } if (listen(s->listen_sock_, 3)) { std::stringstream ss; ss << "listen() error " << errno << "(" << strerror(errno) << ")"; std::cerr << ss.str() << std::endl; return NULL; } std::cout << "Server accepting connections at " << s->ip_ << ":" << s->port_ << std::endl; { timeval tv = { 30, 0 }; int sock = s->acceptConnection(tv); if (sock < 0) { std::cout << "no connections accepted" << std::endl; return NULL; } dumpReceivedContent(sock); sendMinHttpResponse(sock); shutdown(sock, SHUT_RDWR); close(sock); } return NULL; } }; const std::string Server::ip_{IP}; int main( int argc, char* argv[] ) { Server s; return 0; }
核心问题
将Chrome和Chromium指向服务器地址127.0.0.23:9018时,页面为空白无内容;但Firefox能正常显示预期的"Hello World!"字符串。为何会出现这种浏览器兼容性差异?
内容的提问来源于stack exchange,提问作者StoneThrow
相关产品推荐
相关产品推荐

