diff options
22 files changed, 522 insertions, 114 deletions
diff --git a/chrome/browser/debugger/devtools_protocol_handler.cc b/chrome/browser/debugger/devtools_protocol_handler.cc index d88aad0..4a73efa 100644 --- a/chrome/browser/debugger/devtools_protocol_handler.cc +++ b/chrome/browser/debugger/devtools_protocol_handler.cc @@ -32,7 +32,7 @@ void DevToolsProtocolHandler::Start() { void DevToolsProtocolHandler::Init() { server_ = DevToolsRemoteListenSocket::Listen( - "127.0.0.1", port_, this, this); + "127.0.0.1", port_, this); } void DevToolsProtocolHandler::Stop() { @@ -85,26 +85,14 @@ void DevToolsProtocolHandler::Send(const DevToolsRemoteMessage& message) { } } -void DevToolsProtocolHandler::DidAccept(ListenSocket *server, - ListenSocket *connection) { +void DevToolsProtocolHandler::OnAcceptConnection(ListenSocket *connection) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - if (connection_ == NULL) { - connection_ = connection; - connection_->AddRef(); - } - // else the connection will get deleted itself with scoped_refptr -} - -void DevToolsProtocolHandler::DidRead(ListenSocket *connection, - const std::string& data) { - // Not used. + connection_ = connection; } -void DevToolsProtocolHandler::DidClose(ListenSocket *sock) { +void DevToolsProtocolHandler::OnConnectionLost() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); - DCHECK(connection_ == sock); connection_ = NULL; - sock->Release(); for (ToolToListenerMap::const_iterator it = tool_to_listener_map_.begin(), end = tool_to_listener_map_.end(); it != end; diff --git a/chrome/browser/debugger/devtools_protocol_handler.h b/chrome/browser/debugger/devtools_protocol_handler.h index 8c14a82..fff65eb 100644 --- a/chrome/browser/debugger/devtools_protocol_handler.h +++ b/chrome/browser/debugger/devtools_protocol_handler.h @@ -21,8 +21,7 @@ class DevToolsRemoteMessage; // based on the "Tool" message header value. class DevToolsProtocolHandler : public DevToolsRemoteListener, - public OutboundSocketDelegate, - public ListenSocket::ListenSocketDelegate { + public OutboundSocketDelegate { public: typedef base::hash_map< std::string, scoped_refptr<DevToolsRemoteListener> > ToolToListenerMap; @@ -54,16 +53,12 @@ class DevToolsProtocolHandler // DevToolsRemoteListener interface virtual void HandleMessage(const DevToolsRemoteMessage& message); - virtual void OnConnectionLost() {} + virtual void OnAcceptConnection(ListenSocket *connection); + virtual void OnConnectionLost(); // OutboundSocketDelegate interface virtual void Send(const DevToolsRemoteMessage& message); - // ListenSocket::ListenSocketDelegate interface - virtual void DidAccept(ListenSocket *server, ListenSocket *connection); - virtual void DidRead(ListenSocket *connection, const std::string& data); - virtual void DidClose(ListenSocket *sock); - private: virtual ~DevToolsProtocolHandler(); diff --git a/chrome/browser/debugger/devtools_remote.h b/chrome/browser/debugger/devtools_remote.h index 713df3d..a0edc6d 100644 --- a/chrome/browser/debugger/devtools_remote.h +++ b/chrome/browser/debugger/devtools_remote.h @@ -9,6 +9,7 @@ #include "base/ref_counted.h" class DevToolsRemoteMessage; +class ListenSocket; // This interface should be implemented by a class that wants to handle // DevToolsRemoteMessages dispatched by some entity. It must extend @@ -20,6 +21,7 @@ class DevToolsRemoteListener // This method is invoked on the UI thread whenever the debugger connection // has been lost. virtual void OnConnectionLost() = 0; + virtual void OnAcceptConnection(ListenSocket* connection) {} protected: friend class base::RefCountedThreadSafe<DevToolsRemoteListener>; diff --git a/chrome/browser/debugger/devtools_remote_listen_socket.cc b/chrome/browser/debugger/devtools_remote_listen_socket.cc index e4a84cb..87b88d0 100644 --- a/chrome/browser/debugger/devtools_remote_listen_socket.cc +++ b/chrome/browser/debugger/devtools_remote_listen_socket.cc @@ -41,9 +41,8 @@ const int kReadBufSize = 200; DevToolsRemoteListenSocket::DevToolsRemoteListenSocket( SOCKET s, - ListenSocketDelegate* del, DevToolsRemoteListener* message_listener) - : ListenSocket(s, del), + : ALLOW_THIS_IN_INITIALIZER_LIST(ListenSocket(s, this)), state_(HANDSHAKE), remaining_payload_length_(0), message_listener_(message_listener), @@ -86,55 +85,30 @@ DevToolsRemoteListenSocket::~DevToolsRemoteListenSocket() {} DevToolsRemoteListenSocket* DevToolsRemoteListenSocket::Listen(const std::string& ip, int port, - ListenSocketDelegate* del, DevToolsRemoteListener* listener) { SOCKET s = ListenSocket::Listen(ip, port); if (s == INVALID_SOCKET) { // TODO(apavlov): error handling } else { DevToolsRemoteListenSocket* sock = - new DevToolsRemoteListenSocket(s, del, listener); + new DevToolsRemoteListenSocket(s, listener); sock->Listen(); return sock; } return NULL; } -void DevToolsRemoteListenSocket::Read() { - char buf[kReadBufSize]; - int len; - do { - len = HANDLE_EINTR(recv(socket_, buf, kReadBufSize, 0)); - if (len == SOCKET_ERROR) { -#if defined(OS_WIN) - int err = WSAGetLastError(); - if (err == WSAEWOULDBLOCK) { -#elif defined(OS_POSIX) - if (errno == EWOULDBLOCK || errno == EAGAIN) { -#endif - break; - } else { - // TODO(apavlov): some error handling required here - break; - } - } else if (len == 0) { - // In Windows, Close() is called by OnObjectSignaled. In POSIX, we need - // to call it here. -#if defined(OS_POSIX) - Close(); -#endif - } else { - // TODO(apavlov): maybe change DidRead to take a length instead - DCHECK(len > 0 && len <= kReadBufSize); - this->DispatchRead(buf, len); - } - } while (len == kReadBufSize); +void DevToolsRemoteListenSocket::DidAccept(ListenSocket *server, + ListenSocket *connection) { + connection->AddRef(); + message_listener_->OnAcceptConnection(connection); } // Dispatches data from socket to socket_delegate_, extracting messages // delimited by newlines. -void DevToolsRemoteListenSocket::DispatchRead(char* buf, int len) { - char* pBuf = buf; +void DevToolsRemoteListenSocket::DidRead(ListenSocket* connection, + const char* pBuf, + int len) { while (len > 0) { if (state_ != PAYLOAD) { if (cr_received_ && *pBuf == '\n') { @@ -173,6 +147,11 @@ void DevToolsRemoteListenSocket::DispatchRead(char* buf, int len) { } } +void DevToolsRemoteListenSocket::DidClose(ListenSocket *connection) { + message_listener_->OnConnectionLost(); + connection->Release(); +} + void DevToolsRemoteListenSocket::DispatchField() { static const std::string kHandshakeString = "ChromeDevToolsHandshake"; switch (state_) { @@ -231,7 +210,6 @@ void DevToolsRemoteListenSocket::Accept() { if (conn != INVALID_SOCKET) { scoped_refptr<DevToolsRemoteListenSocket> sock = new DevToolsRemoteListenSocket(conn, - socket_delegate_, message_listener_); // it's up to the delegate to AddRef if it wants to keep it around #if defined(OS_POSIX) diff --git a/chrome/browser/debugger/devtools_remote_listen_socket.h b/chrome/browser/debugger/devtools_remote_listen_socket.h index d53364f..9424a76 100644 --- a/chrome/browser/debugger/devtools_remote_listen_socket.h +++ b/chrome/browser/debugger/devtools_remote_listen_socket.h @@ -14,26 +14,30 @@ class DevToolsRemoteListener; // Listens to remote debugger incoming connections, handles the V8ARDP protocol // socket input and invokes the message handler when appropriate. -class DevToolsRemoteListenSocket : public ListenSocket { +class DevToolsRemoteListenSocket : public ListenSocket, + public ListenSocket::ListenSocketDelegate { public: // Listen on port for the specified IP address. Use 127.0.0.1 to only // accept local connections. static DevToolsRemoteListenSocket* Listen( const std::string& ip, int port, - ListenSocketDelegate* del, DevToolsRemoteListener* message_listener); protected: virtual void Listen() { ListenSocket::Listen(); } virtual void Accept(); - virtual void Read(); virtual void Close(); virtual void SendInternal(const char* bytes, int len); private: virtual ~DevToolsRemoteListenSocket(); + // ListenSocket::ListenSocketDelegate interface + virtual void DidAccept(ListenSocket *server, ListenSocket *connection); + virtual void DidRead(ListenSocket *connection, const char* data, int len); + virtual void DidClose(ListenSocket *connection); + // The protocol states while reading socket input enum State { INVALID = 0, // Bad handshake message received, retry @@ -43,11 +47,9 @@ class DevToolsRemoteListenSocket : public ListenSocket { }; DevToolsRemoteListenSocket(SOCKET s, - ListenSocketDelegate *del, DevToolsRemoteListener *listener); void StartNextField(); void HandleMessage(); - void DispatchRead(char* buf, int len); void DispatchField(); const std::string& GetHeader(const std::string& header_name, const std::string& default_value) const; diff --git a/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc index 5a2688d..79933fe 100644 --- a/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc +++ b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.cc @@ -48,7 +48,7 @@ static const char* kSemaphoreName = "chromium.listen_socket"; ListenSocket* DevToolsRemoteListenSocketTester::DoListen() { - return DevToolsRemoteListenSocket::Listen(kLoopback, kTestPort, this, this); + return DevToolsRemoteListenSocket::Listen(kLoopback, kTestPort, this); } void DevToolsRemoteListenSocketTester::SetUp() { @@ -211,8 +211,6 @@ int DevToolsRemoteListenSocketTester::ClearTestSocket() { } void DevToolsRemoteListenSocketTester::Shutdown() { - connection_->Release(); - connection_ = NULL; server_->Release(); server_ = NULL; ReportAction(ListenSocketTestAction(ACTION_SHUTDOWN)); @@ -220,10 +218,8 @@ void DevToolsRemoteListenSocketTester::Shutdown() { void DevToolsRemoteListenSocketTester::Listen() { server_ = DoListen(); - if (server_) { - server_->AddRef(); - ReportAction(ListenSocketTestAction(ACTION_LISTEN)); - } + server_->AddRef(); + ReportAction(ListenSocketTestAction(ACTION_LISTEN)); } void DevToolsRemoteListenSocketTester::SendFromTester() { @@ -231,19 +227,14 @@ void DevToolsRemoteListenSocketTester::SendFromTester() { ReportAction(ListenSocketTestAction(ACTION_SEND)); } -void DevToolsRemoteListenSocketTester::DidAccept(ListenSocket *server, - ListenSocket *connection) { +void DevToolsRemoteListenSocketTester::OnAcceptConnection( + ListenSocket* connection) { connection_ = connection; - connection_->AddRef(); ReportAction(ListenSocketTestAction(ACTION_ACCEPT)); } -void DevToolsRemoteListenSocketTester::DidRead(ListenSocket *connection, - const std::string& data) { - ReportAction(ListenSocketTestAction(ACTION_READ, data)); -} - -void DevToolsRemoteListenSocketTester::DidClose(ListenSocket *sock) { +void DevToolsRemoteListenSocketTester::OnConnectionLost() { + connection_ = NULL; ReportAction(ListenSocketTestAction(ACTION_CLOSE)); } diff --git a/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h index 91db537..b4987b8 100644 --- a/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h +++ b/chrome/browser/debugger/devtools_remote_listen_socket_unittest.h @@ -78,7 +78,6 @@ class ListenSocketTestAction { // This had to be split out into a separate class because I couldn't // make a the testing::Test class refcounted. class DevToolsRemoteListenSocketTester : - public ListenSocket::ListenSocketDelegate, public DevToolsRemoteListener { public: DevToolsRemoteListenSocketTester() @@ -99,7 +98,8 @@ class DevToolsRemoteListenSocketTester : // DevToolsRemoteMessageHandler interface virtual void HandleMessage(const DevToolsRemoteMessage& message); - virtual void OnConnectionLost() {} + virtual void OnAcceptConnection(ListenSocket* connection); + virtual void OnConnectionLost(); // read all pending data from the test socket int ClearTestSocket(); @@ -107,9 +107,6 @@ class DevToolsRemoteListenSocketTester : void Shutdown(); void Listen(); void SendFromTester(); - virtual void DidAccept(ListenSocket *server, ListenSocket *connection); - virtual void DidRead(ListenSocket *connection, const std::string& data); - virtual void DidClose(ListenSocket *sock); virtual bool Send(SOCKET sock, const std::string& str); // verify the send/read from client to server void TestClientSend(); diff --git a/chrome_frame/test/test_server.cc b/chrome_frame/test/test_server.cc index 942333a..88672b8 100644 --- a/chrome_frame/test/test_server.cc +++ b/chrome_frame/test/test_server.cc @@ -172,11 +172,13 @@ void SimpleWebServer::DidAccept(ListenSocket* server, } void SimpleWebServer::DidRead(ListenSocket* connection, - const std::string& data) { + const char* data, + int len) { Connection* c = FindConnection(connection); DCHECK(c); Request& r = c->request(); - r.OnDataReceived(data); + std::string str(data, len); + r.OnDataReceived(str); if (r.AllContentReceived()) { const Request& request = c->request(); Response* response = FindResponse(request); @@ -247,11 +249,14 @@ void HTTPTestServer::DidAccept(ListenSocket* server, ListenSocket* socket) { connection_list_.push_back(new ConfigurableConnection(socket)); } -void HTTPTestServer::DidRead(ListenSocket* socket, const std::string& data) { +void HTTPTestServer::DidRead(ListenSocket* socket, + const char* data, + int len) { scoped_refptr<ConfigurableConnection> connection = ConnectionFromSocket(socket); if (connection) { - connection->r_.OnDataReceived(data); + std::string str(data, len); + connection->r_.OnDataReceived(str); if (connection->r_.AllContentReceived()) { if (LowerCaseEqualsASCII(connection->r_.method(), "post")) this->Post(connection, connection->r_.path(), connection->r_); diff --git a/chrome_frame/test/test_server.h b/chrome_frame/test/test_server.h index b827ceb..c3fab96 100644 --- a/chrome_frame/test/test_server.h +++ b/chrome_frame/test/test_server.h @@ -288,7 +288,7 @@ class SimpleWebServer : public ListenSocket::ListenSocketDelegate { // ListenSocketDelegate overrides. virtual void DidAccept(ListenSocket* server, ListenSocket* connection); - virtual void DidRead(ListenSocket* connection, const std::string& data); + virtual void DidRead(ListenSocket* connection, const char* data, int len); virtual void DidClose(ListenSocket* sock); const ConnectionList& connections() const { @@ -381,7 +381,7 @@ class HTTPTestServer : public ListenSocket::ListenSocketDelegate { virtual void Post(ConfigurableConnection* connection, const std::string& path, const Request& r) = 0; -private: + private: typedef std::list<scoped_refptr<ConfigurableConnection> > ConnectionList; ConnectionList::iterator FindConnection(const ListenSocket* socket); scoped_refptr<ConfigurableConnection> ConnectionFromSocket( @@ -389,7 +389,7 @@ private: // ListenSocketDelegate overrides. virtual void DidAccept(ListenSocket* server, ListenSocket* socket); - virtual void DidRead(ListenSocket* socket, const std::string& data); + virtual void DidRead(ListenSocket* socket, const char* data, int len); virtual void DidClose(ListenSocket* socket); scoped_refptr<ListenSocket> server_; diff --git a/net/base/listen_socket.cc b/net/base/listen_socket.cc index c2eb003..0cb529d 100644 --- a/net/base/listen_socket.cc +++ b/net/base/listen_socket.cc @@ -31,7 +31,7 @@ typedef int socklen_t; namespace { -const int kReadBufSize = 200; +const int kReadBufSize = 4096; } // namespace @@ -160,7 +160,7 @@ void ListenSocket::Read() { // TODO(ibrar): maybe change DidRead to take a length instead DCHECK(len > 0 && len <= kReadBufSize); buf[len] = 0; // already create a buffer with +1 length - socket_delegate_->DidRead(this, buf); + socket_delegate_->DidRead(this, buf, len); } } while (len == kReadBufSize); } @@ -206,19 +206,32 @@ void ListenSocket::WatchSocket(WaitState state) { } void ListenSocket::SendInternal(const char* bytes, int len) { - int sent = HANDLE_EINTR(send(socket_, bytes, len, 0)); - if (sent == kSocketError) { + char* send_buf = const_cast<char *>(bytes); + int len_left = len; + while (true) { + int sent = HANDLE_EINTR(send(socket_, send_buf, len_left, 0)); + if (sent == len_left) { // A shortcut to avoid extraneous checks. + break; + } + if (sent == kSocketError) { #if defined(OS_WIN) - int err = WSAGetLastError(); - if (err == WSAEWOULDBLOCK) { + if (WSAGetLastError() != WSAEWOULDBLOCK) { + LOG(ERROR) << "send failed: WSAGetLastError()==" << WSAGetLastError(); #elif defined(OS_POSIX) - if (errno == EWOULDBLOCK || errno == EAGAIN) { + if (errno != EWOULDBLOCK && errno != EAGAIN) { + LOG(ERROR) << "send failed: errno==" << errno; #endif - // TODO(ibrar): there should be logic here to handle this because - // it is not an error + break; + } + // Otherwise we would block, and now we have to wait for a retry. + // Fall through to PlatformThread::YieldCurrentThread() + } else { + // sent != len_left according to the shortcut above. + // Shift the buffer start and send the remainder after a short while. + send_buf += sent; + len_left -= sent; } - } else if (sent != len) { - LOG(ERROR) << "send failed: "; + PlatformThread::YieldCurrentThread(); } } diff --git a/net/base/listen_socket.h b/net/base/listen_socket.h index 585a094..923d361 100644 --- a/net/base/listen_socket.h +++ b/net/base/listen_socket.h @@ -51,7 +51,9 @@ class ListenSocket : public base::RefCountedThreadSafe<ListenSocket>, // Socket that was created. Ownership of connection is transferred // to the delegate with this call. virtual void DidAccept(ListenSocket *server, ListenSocket *connection) = 0; - virtual void DidRead(ListenSocket *connection, const std::string& data) = 0; + virtual void DidRead(ListenSocket *connection, + const char* data, + int len) = 0; virtual void DidClose(ListenSocket *sock) = 0; }; diff --git a/net/base/listen_socket_unittest.cc b/net/base/listen_socket_unittest.cc index ed2b566..f33ac3c 100644 --- a/net/base/listen_socket_unittest.cc +++ b/net/base/listen_socket_unittest.cc @@ -212,8 +212,10 @@ void ListenSocketTester::DidAccept(ListenSocket *server, } void ListenSocketTester::DidRead(ListenSocket *connection, - const std::string& data) { - ReportAction(ListenSocketTestAction(ACTION_READ, data)); + const char* data, + int len) { + std::string str(data, len); + ReportAction(ListenSocketTestAction(ACTION_READ, str)); } void ListenSocketTester::DidClose(ListenSocket *sock) { diff --git a/net/base/listen_socket_unittest.h b/net/base/listen_socket_unittest.h index 87bb24b..7adea1e 100644 --- a/net/base/listen_socket_unittest.h +++ b/net/base/listen_socket_unittest.h @@ -94,7 +94,7 @@ class ListenSocketTester : void Listen(); void SendFromTester(); virtual void DidAccept(ListenSocket *server, ListenSocket *connection); - virtual void DidRead(ListenSocket *connection, const std::string& data); + virtual void DidRead(ListenSocket *connection, const char* data, int len); virtual void DidClose(ListenSocket *sock); virtual bool Send(SOCKET sock, const std::string& str); // verify the send/read from client to server diff --git a/net/base/telnet_server.cc b/net/base/telnet_server.cc index 33eb5b1..5b027d7 100644 --- a/net/base/telnet_server.cc +++ b/net/base/telnet_server.cc @@ -197,7 +197,9 @@ void TelnetServer::StateMachineStep(unsigned char c) { case EXPECTING_NEW_LINE: if (c == TelnetProtocol::LF) { Send("\n", 1); - socket_delegate_->DidRead(this, command_line_); + socket_delegate_->DidRead(this, + command_line_.c_str(), + command_line_.length()); command_line_ = ""; } input_state_ = NOT_IN_IAC_OR_ESC_SEQUENCE; diff --git a/net/net.gyp b/net/net.gyp index 96aeb1f..d6f1921 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -987,6 +987,21 @@ ], }, { + 'target_name': 'http_listen_socket', + 'type': '<(library)', + 'dependencies': [ + 'net', + '../base/base.gyp:base', + '../testing/gtest.gyp:gtest', + ], + 'msvs_guid': 'FCB894A4-CC6C-48C2-B495-52C80527E9BE', + 'sources': [ + 'server/http_listen_socket.cc', + 'server/http_listen_socket.h', + 'server/http_server_request_info.h', + ], + }, + { 'target_name': 'hresolv', 'type': 'executable', 'dependencies': [ diff --git a/net/server/http_listen_socket.cc b/net/server/http_listen_socket.cc new file mode 100644 index 0000000..8d73abc --- /dev/null +++ b/net/server/http_listen_socket.cc @@ -0,0 +1,316 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/md5.h" +#include "base/string_util.h" +#include "net/server/http_listen_socket.h" +#include "net/server/http_server_request_info.h" + +// must run in the IO thread +HttpListenSocket::HttpListenSocket(SOCKET s, + HttpListenSocket::Delegate* delegate) + : ALLOW_THIS_IN_INITIALIZER_LIST(ListenSocket(s, this)), + delegate_(delegate), + is_web_socket_(false) { +} + +// must run in the IO thread +HttpListenSocket::~HttpListenSocket() { +} + +void HttpListenSocket::Accept() { + SOCKET conn = ListenSocket::Accept(socket_); + DCHECK_NE(conn, ListenSocket::kInvalidSocket); + if (conn == ListenSocket::kInvalidSocket) { + // TODO + } else { + scoped_refptr<HttpListenSocket> sock = + new HttpListenSocket(conn, delegate_); + // it's up to the delegate to AddRef if it wants to keep it around + DidAccept(this, sock); + } +} + +HttpListenSocket* HttpListenSocket::Listen( + const std::string& ip, + int port, + HttpListenSocket::Delegate* delegate) { + SOCKET s = ListenSocket::Listen(ip, port); + if (s == ListenSocket::kInvalidSocket) { + // TODO (ibrar): error handling + } else { + HttpListenSocket *serv = new HttpListenSocket(s, delegate); + serv->Listen(); + return serv; + } + return NULL; +} + +std::string GetHeaderValue( + HttpServerRequestInfo* request, + const std::string& header_name) { + HttpServerRequestInfo::HeadersMap::iterator it = + request->headers.find(header_name); + if (it != request->headers.end()) + return it->second; + return ""; +} + +uint32 WebSocketKeyFingerprint(const std::string& str) { + std::string result; + const char* pChar = str.c_str(); + int length = str.length(); + int spaces = 0; + for (int i = 0; i < length; ++i) { + if (pChar[i] >= '0' && pChar[i] <= '9') + result.append(&pChar[i], 1); + else if (pChar[i] == ' ') + spaces++; + } + if (spaces == 0) + return 0; + int64 number = 0; + if (!StringToInt64(result, &number)) + return 0; + return htonl(static_cast<uint32>(number / spaces)); +} + +void HttpListenSocket::AcceptWebSocket(HttpServerRequestInfo* request) { + std::string key1 = GetHeaderValue(request, "Sec-WebSocket-Key1"); + std::string key2 = GetHeaderValue(request, "Sec-WebSocket-Key2"); + + uint32 fp1 = WebSocketKeyFingerprint(key1); + uint32 fp2 = WebSocketKeyFingerprint(key2); + + char data[16]; + memcpy(data, &fp1, 4); + memcpy(data + 4, &fp2, 4); + memcpy(data + 8, &request->data[0], 8); + + MD5Digest digest; + MD5Sum(data, 16, &digest); + + std::string origin = GetHeaderValue(request, "Origin"); + std::string host = GetHeaderValue(request, "Host"); + std::string location = "ws://" + host + request->path; + is_web_socket_ = true; + Send(StringPrintf("HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Origin: %s\r\n" + "Sec-WebSocket-Location: %s\r\n" + "\r\n", + origin.c_str(), + location.c_str())); + Send(reinterpret_cast<char*>(digest.a), 16); +} + +void HttpListenSocket::SendOverWebSocket(const std::string& data) { + DCHECK(is_web_socket_); + char message_start = 0; + char message_end = -1; + Send(&message_start, 1); + Send(data); + Send(&message_end, 1); +} + +// +// HTTP Request Parser +// This HTTP request parser uses a simple state machine to quickly parse +// through the headers. The parser is not 100% complete, as it is designed +// for use in this simple test driver. +// +// Known issues: +// - does not handle whitespace on first HTTP line correctly. Expects +// a single space between the method/url and url/protocol. + +// Input character types. +enum header_parse_inputs { + INPUT_SPACE, + INPUT_CR, + INPUT_LF, + INPUT_COLON, + INPUT_00, + INPUT_FF, + INPUT_DEFAULT, + MAX_INPUTS, +}; + +// Parser states. +enum header_parse_states { + ST_METHOD, // Receiving the method + ST_URL, // Receiving the URL + ST_PROTO, // Receiving the protocol + ST_HEADER, // Starting a Request Header + ST_NAME, // Receiving a request header name + ST_SEPARATOR, // Receiving the separator between header name and value + ST_VALUE, // Receiving a request header value + ST_WS_READY, // Ready to receive web socket frame + ST_WS_FRAME, // Receiving WebSocket frame + ST_WS_CLOSE, // Closing the connection WebSocket connection + ST_DONE, // Parsing is complete and successful + ST_ERR, // Parsing encountered invalid syntax. + MAX_STATES +}; + +// State transition table +int parser_state[MAX_STATES][MAX_INPUTS] = { +/* METHOD */ { ST_URL, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_METHOD }, +/* URL */ { ST_PROTO, ST_ERR, ST_ERR, ST_URL, ST_ERR, ST_ERR, ST_URL }, +/* PROTOCOL */ { ST_ERR, ST_HEADER, ST_NAME, ST_ERR, ST_ERR, ST_ERR, ST_PROTO }, +/* HEADER */ { ST_ERR, ST_ERR, ST_NAME, ST_ERR, ST_ERR, ST_ERR, ST_ERR }, +/* NAME */ { ST_SEPARATOR, ST_DONE, ST_ERR, ST_SEPARATOR, ST_ERR, ST_ERR, ST_NAME }, +/* SEPARATOR */ { ST_SEPARATOR, ST_ERR, ST_ERR, ST_SEPARATOR, ST_ERR, ST_ERR, ST_VALUE }, +/* VALUE */ { ST_VALUE, ST_HEADER, ST_NAME, ST_VALUE, ST_ERR, ST_ERR, ST_VALUE }, +/* WS_READY */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_WS_FRAME, ST_WS_CLOSE, ST_ERR}, +/* WS_FRAME */ { ST_WS_FRAME, ST_WS_FRAME, ST_WS_FRAME, ST_WS_FRAME, ST_ERR, ST_WS_READY, ST_WS_FRAME }, +/* WS_CLOSE */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_WS_CLOSE, ST_ERR, ST_ERR }, +/* DONE */ { ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE, ST_DONE }, +/* ERR */ { ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR, ST_ERR } +}; + +// Convert an input character to the parser's input token. +int charToInput(char ch) { + switch(ch) { + case ' ': + return INPUT_SPACE; + case '\r': + return INPUT_CR; + case '\n': + return INPUT_LF; + case ':': + return INPUT_COLON; + case 0x0: + return INPUT_00; + case -1: + return INPUT_FF; + } + return INPUT_DEFAULT; +} + +HttpServerRequestInfo* HttpListenSocket::ParseHeaders() { + int pos = 0; + int data_len = recv_data_.length(); + int state = is_web_socket_ ? ST_WS_READY : ST_METHOD; + scoped_ptr<HttpServerRequestInfo> info(new HttpServerRequestInfo()); + std::string buffer; + std::string header_name; + std::string header_value; + while (pos < data_len) { + char ch = recv_data_[pos++]; + int input = charToInput(ch); + int next_state = parser_state[state][input]; + + bool transition = (next_state != state); + if (transition) { + // Do any actions based on state transitions. + switch (state) { + case ST_METHOD: + info->method = buffer; + buffer.clear(); + break; + case ST_URL: + info->path = buffer; + buffer.clear(); + break; + case ST_PROTO: + // TODO(mbelshe): Deal better with parsing protocol. + DCHECK(buffer == "HTTP/1.1"); + buffer.clear(); + break; + case ST_NAME: + header_name = buffer; + buffer.clear(); + break; + case ST_VALUE: + header_value = buffer; + // TODO(mbelshe): Deal better with duplicate headers + DCHECK(info->headers.find(header_name) == info->headers.end()); + info->headers[header_name] = header_value; + buffer.clear(); + break; + case ST_SEPARATOR: + buffer.append(&ch, 1); + break; + case ST_WS_FRAME: + recv_data_ = recv_data_.substr(pos); + info->data = buffer; + buffer.clear(); + return info.release(); + break; + } + state = next_state; + } else { + // Do any actions based on current state + switch (state) { + case ST_METHOD: + case ST_URL: + case ST_PROTO: + case ST_VALUE: + case ST_NAME: + case ST_WS_FRAME: + buffer.append(&ch, 1); + break; + case ST_DONE: + recv_data_ = recv_data_.substr(pos); + info->data = recv_data_; + recv_data_.clear(); + return info.release(); + case ST_WS_CLOSE: + is_web_socket_ = false; + return NULL; + case ST_ERR: + return NULL; + } + } + } + // No more characters, but we haven't finished parsing yet. + return NULL; +} + +void HttpListenSocket::DidAccept(ListenSocket* server, + ListenSocket* connection) { + connection->AddRef(); +} + +void HttpListenSocket::DidRead(ListenSocket*, + const char* data, + int len) { + recv_data_.append(data, len); + while (recv_data_.length()) { + scoped_ptr<HttpServerRequestInfo> request(ParseHeaders()); + if (!request.get()) + break; + + if (is_web_socket_) { + delegate_->OnWebSocketMessage(this, request->data); + continue; + } + + std::string connection = GetHeaderValue(request.get(), "Connection"); + if (connection == "Upgrade") { + // Is this WebSocket and if yes, upgrade the connection. + std::string key1 = GetHeaderValue(request.get(), "Sec-WebSocket-Key1"); + std::string key2 = GetHeaderValue(request.get(), "Sec-WebSocket-Key2"); + if (!key1.empty() && !key2.empty()) { + delegate_->OnWebSocketRequest(this, request.get()); + continue; + } + } + delegate_->OnHttpRequest(this, request.get()); + } +} + +void HttpListenSocket::DidClose(ListenSocket* sock) { + sock->Release(); + delegate_->OnClose(this); +} diff --git a/net/server/http_listen_socket.h b/net/server/http_listen_socket.h new file mode 100644 index 0000000..5517af0 --- /dev/null +++ b/net/server/http_listen_socket.h @@ -0,0 +1,65 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SERVER_HTTP_LISTEN_SOCKET_H_ +#define NET_SERVER_HTTP_LISTEN_SOCKET_H_ + +#include "net/base/listen_socket.h" + +class HttpServerRequestInfo; + +// Implements a simple HTTP listen socket on top of the raw socket interface. +class HttpListenSocket : public ListenSocket, + public ListenSocket::ListenSocketDelegate { + public: + class Delegate { + public: + virtual void OnHttpRequest(HttpListenSocket* socket, + HttpServerRequestInfo* info) = 0; + + virtual void OnWebSocketRequest(HttpListenSocket* socket, + HttpServerRequestInfo* info) = 0; + + virtual void OnWebSocketMessage(HttpListenSocket* socket, + const std::string& data) = 0; + + virtual void OnClose(HttpListenSocket* socket) = 0; + protected: + virtual ~Delegate() {} + }; + + static HttpListenSocket* Listen(const std::string& ip, + int port, + HttpListenSocket::Delegate* delegate); + + void AcceptWebSocket(HttpServerRequestInfo* request); + + void SendOverWebSocket(const std::string& data); + + void Listen() { ListenSocket::Listen(); } + virtual void Accept(); + + // ListenSocketDelegate + virtual void DidAccept(ListenSocket* server, ListenSocket* connection); + virtual void DidRead(ListenSocket* connection, const char* data, int len); + virtual void DidClose(ListenSocket* sock); + + private: + static const int kReadBufSize = 16 * 1024; + HttpListenSocket(SOCKET s, HttpListenSocket::Delegate* del); + virtual ~HttpListenSocket(); + + // Expects the raw data to be stored in recv_data_. If parsing is successful, + // will remove the data parsed from recv_data_, leaving only the unused + // recv data. + HttpServerRequestInfo* ParseHeaders(); + + HttpListenSocket::Delegate* delegate_; + bool is_web_socket_; + std::string recv_data_; + + DISALLOW_COPY_AND_ASSIGN(HttpListenSocket); +}; + +#endif // NET_SERVER_HTTP_LISTEN_SOCKET_H_ diff --git a/net/server/http_server_request_info.h b/net/server/http_server_request_info.h new file mode 100644 index 0000000..64f0a78a --- /dev/null +++ b/net/server/http_server_request_info.h @@ -0,0 +1,34 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_ +#define NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_ + +#include <string> + +#include "net/http/http_request_info.h" + +// Meta information about an HTTP request. +// This is geared toward servers in that it keeps a map of the headers and +// values rather than just a list of header strings (which net::HttpRequestInfo +// does). +class HttpServerRequestInfo { + public: + HttpServerRequestInfo() {} + + // Request method. + std::string method; + + // Request line. + std::string path; + + // Request data. + std::string data; + + // A map of the names -> values for HTTP headers. + typedef std::map<std::string, std::string> HeadersMap; + HeadersMap headers; +}; + +#endif // NET_SERVER_HTTP_SERVER_REQUEST_INFO_H_ diff --git a/net/socket/tcp_client_socket_unittest.cc b/net/socket/tcp_client_socket_unittest.cc index 30628d3..52cdb88 100644 --- a/net/socket/tcp_client_socket_unittest.cc +++ b/net/socket/tcp_client_socket_unittest.cc @@ -33,7 +33,7 @@ class TCPClientSocketTest virtual void DidAccept(ListenSocket* server, ListenSocket* connection) { connected_sock_ = connection; } - virtual void DidRead(ListenSocket*, const std::string& s) { + virtual void DidRead(ListenSocket*, const char* str, int len) { // TODO(dkegel): this might not be long enough to tickle some bugs. connected_sock_->Send(kServerReply, arraysize(kServerReply) - 1, diff --git a/net/socket/tcp_pinger_unittest.cc b/net/socket/tcp_pinger_unittest.cc index 8cd4ed4..63e0045 100644 --- a/net/socket/tcp_pinger_unittest.cc +++ b/net/socket/tcp_pinger_unittest.cc @@ -28,7 +28,7 @@ class TCPPingerTest LOG(INFO) << "TCPPinger accepted connection"; connected_sock_ = connection; } - virtual void DidRead(ListenSocket*, const std::string& s) { + virtual void DidRead(ListenSocket*, const char* str, int len) { // Not really needed yet, as TCPPinger doesn't support Read connected_sock_->Send(std::string("HTTP/1.1 404 Not Found"), true); connected_sock_ = NULL; diff --git a/net/tools/fetch/http_listen_socket.cc b/net/tools/fetch/http_listen_socket.cc index 033afc4..e135322 100644 --- a/net/tools/fetch/http_listen_socket.cc +++ b/net/tools/fetch/http_listen_socket.cc @@ -182,8 +182,9 @@ void HttpListenSocket::DidAccept(ListenSocket* server, } void HttpListenSocket::DidRead(ListenSocket* connection, - const std::string& data) { - recv_data_ += data; + const char* data, + int len) { + recv_data_.append(data, len); while (recv_data_.length()) { HttpServerRequestInfo* request = ParseHeaders(); if (!request) diff --git a/net/tools/fetch/http_listen_socket.h b/net/tools/fetch/http_listen_socket.h index 15df455..a1b77c5e 100644 --- a/net/tools/fetch/http_listen_socket.h +++ b/net/tools/fetch/http_listen_socket.h @@ -36,7 +36,7 @@ class HttpListenSocket : public ListenSocket, // ListenSocketDelegate virtual void DidAccept(ListenSocket* server, ListenSocket* connection); - virtual void DidRead(ListenSocket* connection, const std::string& data); + virtual void DidRead(ListenSocket* connection, const char* data, int len); virtual void DidClose(ListenSocket* sock); private: |