diff options
author | pfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-05 11:32:40 +0000 |
---|---|---|
committer | pfeldman@chromium.org <pfeldman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-05 11:32:40 +0000 |
commit | 0569d86c57dd9385556e4ab4792e74d552c9df59 (patch) | |
tree | b197fd2a9f1296b7ba49601ba0f31b3ba540de1a /net/server | |
parent | ca311ce0f5002da40625bf87a24858d294d1d1bc (diff) | |
download | chromium_src-0569d86c57dd9385556e4ab4792e74d552c9df59.zip chromium_src-0569d86c57dd9385556e4ab4792e74d552c9df59.tar.gz chromium_src-0569d86c57dd9385556e4ab4792e74d552c9df59.tar.bz2 |
Brushed up listen socket:
- Upstreamed support for partial results from devtools' version
- Made DidRead receive data and length (in order to support websockets data)
- Fixed all the clients.
Added net/server with http socket implementation that supports websockets. Will remove net/tools fetch client and server later.
Review URL: http://codereview.chromium.org/2868036
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@51635 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/server')
-rw-r--r-- | net/server/http_listen_socket.cc | 316 | ||||
-rw-r--r-- | net/server/http_listen_socket.h | 65 | ||||
-rw-r--r-- | net/server/http_server_request_info.h | 34 |
3 files changed, 415 insertions, 0 deletions
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_ |