Index: net/server/http_listen_socket.cc |
diff --git a/net/server/http_listen_socket.cc b/net/server/http_listen_socket.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8d73abc7d06dfd6695a13e15a18aecc2527001c1 |
--- /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); |
+} |