Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/net/tools/flip_server/acceptor_thread.cc b/net/tools/flip_server/acceptor_thread.cc
new file mode 100644
index 0000000..2b65e5d
--- /dev/null
+++ b/net/tools/flip_server/acceptor_thread.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 2012 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.
+
+#include "net/tools/flip_server/acceptor_thread.h"
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h> // For TCP_NODELAY
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <string>
+
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/spdy_ssl.h"
+#include "openssl/err.h"
+#include "openssl/ssl.h"
+
+namespace net {
+
+SMAcceptorThread::SMAcceptorThread(FlipAcceptor* acceptor,
+ MemoryCache* memory_cache)
+ : SimpleThread("SMAcceptorThread"),
+ acceptor_(acceptor),
+ ssl_state_(NULL),
+ use_ssl_(false),
+ idle_socket_timeout_s_(acceptor->idle_socket_timeout_s_),
+ quitting_(false),
+ memory_cache_(memory_cache) {
+ if (!acceptor->ssl_cert_filename_.empty() &&
+ !acceptor->ssl_key_filename_.empty()) {
+ ssl_state_ = new SSLState;
+ bool use_npn = true;
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER) {
+ use_npn = false;
+ }
+ InitSSL(ssl_state_,
+ acceptor_->ssl_cert_filename_,
+ acceptor_->ssl_key_filename_,
+ use_npn,
+ acceptor_->ssl_session_expiry_,
+ acceptor_->ssl_disable_compression_);
+ use_ssl_ = true;
+ }
+}
+
+SMAcceptorThread::~SMAcceptorThread() {
+ for (std::vector<SMConnection*>::iterator i =
+ allocated_server_connections_.begin();
+ i != allocated_server_connections_.end();
+ ++i) {
+ delete *i;
+ }
+ delete ssl_state_;
+}
+
+SMConnection* SMAcceptorThread::NewConnection() {
+ SMConnection* server = SMConnection::NewSMConnection(
+ &epoll_server_, ssl_state_, memory_cache_, acceptor_, "client_conn: ");
+ allocated_server_connections_.push_back(server);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Acceptor: Making new server.";
+ return server;
+}
+
+SMConnection* SMAcceptorThread::FindOrMakeNewSMConnection() {
+ if (unused_server_connections_.empty()) {
+ return NewConnection();
+ }
+ SMConnection* server = unused_server_connections_.back();
+ unused_server_connections_.pop_back();
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Acceptor: Reusing server.";
+ return server;
+}
+
+void SMAcceptorThread::InitWorker() {
+ epoll_server_.RegisterFD(acceptor_->listen_fd_, this, EPOLLIN | EPOLLET);
+}
+
+void SMAcceptorThread::HandleConnection(int server_fd,
+ struct sockaddr_in* remote_addr) {
+ int on = 1;
+ int rc;
+ if (acceptor_->disable_nagle_) {
+ rc = setsockopt(server_fd,
+ IPPROTO_TCP,
+ TCP_NODELAY,
+ reinterpret_cast<char*>(&on),
+ sizeof(on));
+ if (rc < 0) {
+ close(server_fd);
+ LOG(ERROR) << "setsockopt() failed fd=" << server_fd;
+ return;
+ }
+ }
+
+ SMConnection* server_connection = FindOrMakeNewSMConnection();
+ if (server_connection == NULL) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Acceptor: Closing fd " << server_fd;
+ close(server_fd);
+ return;
+ }
+ std::string remote_ip = inet_ntoa(remote_addr->sin_addr);
+ server_connection->InitSMConnection(this,
+ NULL,
+ &epoll_server_,
+ server_fd,
+ std::string(),
+ std::string(),
+ remote_ip,
+ use_ssl_);
+ if (server_connection->initialized())
+ active_server_connections_.push_back(server_connection);
+}
+
+void SMAcceptorThread::AcceptFromListenFD() {
+ if (acceptor_->accepts_per_wake_ > 0) {
+ for (int i = 0; i < acceptor_->accepts_per_wake_; ++i) {
+ struct sockaddr address;
+ socklen_t socklen = sizeof(address);
+ int fd = accept(acceptor_->listen_fd_, &address, &socklen);
+ if (fd == -1) {
+ if (errno != 11) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Acceptor: accept fail("
+ << acceptor_->listen_fd_ << "): " << errno << ": "
+ << strerror(errno);
+ }
+ break;
+ }
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << " Accepted connection";
+ HandleConnection(fd, (struct sockaddr_in*)&address);
+ }
+ } else {
+ while (true) {
+ struct sockaddr address;
+ socklen_t socklen = sizeof(address);
+ int fd = accept(acceptor_->listen_fd_, &address, &socklen);
+ if (fd == -1) {
+ if (errno != 11) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Acceptor: accept fail("
+ << acceptor_->listen_fd_ << "): " << errno << ": "
+ << strerror(errno);
+ }
+ break;
+ }
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Accepted connection";
+ HandleConnection(fd, (struct sockaddr_in*)&address);
+ }
+ }
+}
+
+void SMAcceptorThread::HandleConnectionIdleTimeout() {
+ static time_t oldest_time = time(NULL);
+
+ int cur_time = time(NULL);
+ // Only iterate the list if we speculate that a connection is ready to be
+ // expired
+ if ((cur_time - oldest_time) < idle_socket_timeout_s_)
+ return;
+
+ // TODO(mbelshe): This code could be optimized, active_server_connections_
+ // is already in-order.
+ std::list<SMConnection*>::iterator iter = active_server_connections_.begin();
+ while (iter != active_server_connections_.end()) {
+ SMConnection* conn = *iter;
+ int elapsed_time = (cur_time - conn->last_read_time_);
+ if (elapsed_time > idle_socket_timeout_s_) {
+ conn->Cleanup("Connection idle timeout reached.");
+ iter = active_server_connections_.erase(iter);
+ continue;
+ }
+ if (conn->last_read_time_ < oldest_time)
+ oldest_time = conn->last_read_time_;
+ iter++;
+ }
+ if ((cur_time - oldest_time) >= idle_socket_timeout_s_)
+ oldest_time = cur_time;
+}
+
+void SMAcceptorThread::Run() {
+ while (!quitting_.HasBeenNotified()) {
+ epoll_server_.set_timeout_in_us(10 * 1000); // 10 ms
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ if (tmp_unused_server_connections_.size()) {
+ VLOG(2) << "have " << tmp_unused_server_connections_.size()
+ << " additional unused connections. Total = "
+ << unused_server_connections_.size();
+ unused_server_connections_.insert(unused_server_connections_.end(),
+ tmp_unused_server_connections_.begin(),
+ tmp_unused_server_connections_.end());
+ tmp_unused_server_connections_.clear();
+ }
+ HandleConnectionIdleTimeout();
+ }
+}
+
+void SMAcceptorThread::OnEvent(int fd, EpollEvent* event) {
+ if (event->in_events | EPOLLIN) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "Acceptor: Accepting based upon epoll events";
+ AcceptFromListenFD();
+ }
+}
+
+void SMAcceptorThread::SMConnectionDone(SMConnection* sc) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Done with connection.";
+ tmp_unused_server_connections_.push_back(sc);
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/acceptor_thread.h b/net/tools/flip_server/acceptor_thread.h
new file mode 100644
index 0000000..bcaa43e
--- /dev/null
+++ b/net/tools/flip_server/acceptor_thread.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_ACCEPTOR_THREAD_H_
+#define NET_TOOLS_FLIP_SERVER_ACCEPTOR_THREAD_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/threading/simple_thread.h"
+#include "net/tools/epoll_server/epoll_server.h"
+#include "net/tools/flip_server/sm_interface.h"
+#include "openssl/ssl.h"
+
+struct sockaddr_in;
+
+namespace net {
+
+class FlipAcceptor;
+class MemoryCache;
+class SMConnection;
+struct SSLState;
+
+// TODO(mbelshe): Get rid of this class; we don't need a lock just to set
+// a bool cross threads - especially one which only is set once...
+class Notification {
+ public:
+ explicit Notification(bool value) : value_(value) {}
+
+ void Notify() {
+ base::AutoLock al(lock_);
+ value_ = true;
+ }
+ bool HasBeenNotified() {
+ base::AutoLock al(lock_);
+ return value_;
+ }
+ bool value_;
+ base::Lock lock_;
+};
+
+class SMAcceptorThread : public base::SimpleThread,
+ public EpollCallbackInterface,
+ public SMConnectionPoolInterface {
+ public:
+ SMAcceptorThread(FlipAcceptor* acceptor, MemoryCache* memory_cache);
+ virtual ~SMAcceptorThread();
+
+ // EpollCallbackInteface interface
+ virtual void OnRegistration(EpollServer* eps,
+ int fd,
+ int event_mask) OVERRIDE {}
+ virtual void OnModification(int fd, int event_mask) OVERRIDE {}
+ virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE;
+ virtual void OnUnregistration(int fd, bool replaced) OVERRIDE {}
+ virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE {}
+
+ // SMConnectionPool interface
+ virtual void SMConnectionDone(SMConnection* sc) OVERRIDE;
+
+ // TODO(mbelshe): figure out if we can move these to private functions.
+ SMConnection* NewConnection();
+ SMConnection* FindOrMakeNewSMConnection();
+ void InitWorker();
+ void HandleConnection(int server_fd, struct sockaddr_in* remote_addr);
+ void AcceptFromListenFD();
+
+ // Notify the Accept thread that it is time to terminate.
+ void Quit() { quitting_.Notify(); }
+
+ // Iterates through a list of active connections expiring any that have been
+ // idle longer than the configured timeout.
+ void HandleConnectionIdleTimeout();
+
+ virtual void Run() OVERRIDE;
+
+ private:
+ EpollServer epoll_server_;
+ FlipAcceptor* acceptor_;
+ SSLState* ssl_state_;
+ bool use_ssl_;
+ int idle_socket_timeout_s_;
+
+ std::vector<SMConnection*> unused_server_connections_;
+ std::vector<SMConnection*> tmp_unused_server_connections_;
+ std::vector<SMConnection*> allocated_server_connections_;
+ std::list<SMConnection*> active_server_connections_;
+ Notification quitting_;
+ MemoryCache* memory_cache_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_ACCEPTOR_THREAD_H_
diff --git a/net/tools/flip_server/constants.h b/net/tools/flip_server/constants.h
new file mode 100644
index 0000000..5516509
--- /dev/null
+++ b/net/tools/flip_server/constants.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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_TOOLS_FLIP_SERVER_CONSTANTS_H_
+#define NET_TOOLS_FLIP_SERVER_CONSTANTS_H_
+
+#include "net/spdy/spdy_protocol.h"
+
+const int kMSS = 1460;
+const int kSSLOverhead = 25;
+const int kSpdyOverhead = 8;
+const int kInitialDataSendersThreshold = (2 * kMSS) - kSpdyOverhead;
+const int kSSLSegmentSize = (1 * kMSS) - kSSLOverhead;
+const int kSpdySegmentSize = kSSLSegmentSize - kSpdyOverhead;
+
+#define ACCEPTOR_CLIENT_IDENT \
+ acceptor_->listen_ip_ << ":" << acceptor_->listen_port_ << " "
+
+#define IPV4_PRINTABLE_FORMAT(IP) (((IP)>>0)&0xff), (((IP)>>8)&0xff), \
+ (((IP)>>16)&0xff), (((IP)>>24)&0xff)
+
+#define PIDFILE "/var/run/flip-server.pid"
+
+#endif // NET_TOOLS_FLIP_SERVER_CONSTANTS_H_
diff --git a/net/tools/flip_server/create_listener.cc b/net/tools/flip_server/create_listener.cc
new file mode 100644
index 0000000..c1c0896
--- /dev/null
+++ b/net/tools/flip_server/create_listener.cc
@@ -0,0 +1,296 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/create_listener.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+
+namespace net {
+
+// used to ensure we delete the addrinfo structure
+// alloc'd by getaddrinfo
+class AddrinfoGuard {
+ protected:
+ struct addrinfo* addrinfo_ptr_;
+
+ public:
+ explicit AddrinfoGuard(struct addrinfo* addrinfo_ptr)
+ : addrinfo_ptr_(addrinfo_ptr) {}
+
+ ~AddrinfoGuard() { freeaddrinfo(addrinfo_ptr_); }
+};
+
+// Summary:
+// Closes a socket, with option to attempt it multiple times.
+// Why do this? Well, if the system-call gets interrupted, close
+// can fail with EINTR. In that case you should just retry.. Unfortunately,
+// we can't be sure that errno is properly set since we're using a
+// multithreaded approach in the filter proxy, so we should just retry.
+// Args:
+// fd - the socket to close
+// tries - the number of tries to close the socket.
+// Returns:
+// true - if socket was closed
+// false - if socket was NOT closed.
+// Side-effects:
+// sets *fd to -1 if socket was closed.
+//
+bool CloseSocket(int* fd, int tries) {
+ for (int i = 0; i < tries; ++i) {
+ if (!close(*fd)) {
+ *fd = -1;
+ return true;
+ }
+ }
+ return false;
+}
+
+// Sets an FD to be nonblocking.
+void FlipSetNonBlocking(int fd) {
+ DCHECK_GE(fd, 0);
+
+ int fcntl_return = fcntl(fd, F_GETFL, 0);
+ CHECK_NE(fcntl_return, -1) << "error doing fcntl(fd, F_GETFL, 0) fd: " << fd
+ << " errno=" << errno;
+
+ if (fcntl_return & O_NONBLOCK)
+ return;
+
+ fcntl_return = fcntl(fd, F_SETFL, fcntl_return | O_NONBLOCK);
+ CHECK_NE(fcntl_return, -1)
+ << "error doing fcntl(fd, F_SETFL, fcntl_return) fd: " << fd
+ << " errno=" << errno;
+}
+
+int SetDisableNagle(int fd) {
+ int on = 1;
+ int rc;
+ rc = setsockopt(
+ fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&on), sizeof(on));
+ if (rc < 0) {
+ close(fd);
+ LOG(FATAL) << "setsockopt() TCP_NODELAY: failed on fd " << fd;
+ return 0;
+ }
+ return 1;
+}
+
+// see header for documentation of this function.
+int CreateListeningSocket(const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ int backlog,
+ bool reuseaddr,
+ bool reuseport,
+ bool wait_for_iface,
+ bool disable_nagle,
+ int* listen_fd) {
+ // start out by assuming things will fail.
+ *listen_fd = -1;
+
+ const char* node = NULL;
+ const char* service = NULL;
+
+ if (!host.empty())
+ node = host.c_str();
+ if (!port.empty())
+ service = port.c_str();
+
+ struct addrinfo* results = 0;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+
+ if (is_numeric_host_address) {
+ hints.ai_flags = AI_NUMERICHOST; // iff you know the name is numeric.
+ }
+ hints.ai_flags |= AI_PASSIVE;
+
+ hints.ai_family = PF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err = 0;
+ if ((err = getaddrinfo(node, service, &hints, &results))) {
+ // gai_strerror -is- threadsafe, so we get to use it here.
+ LOG(ERROR) << "getaddrinfo "
+ << " for (" << host << ":" << port << ") " << gai_strerror(err)
+ << "\n";
+ return -1;
+ }
+ // this will delete the addrinfo memory when we return from this function.
+ AddrinfoGuard addrinfo_guard(results);
+
+ int sock =
+ socket(results->ai_family, results->ai_socktype, results->ai_protocol);
+ if (sock == -1) {
+ LOG(ERROR) << "Unable to create socket for (" << host << ":" << port
+ << "): " << strerror(errno) << "\n";
+ return -1;
+ }
+
+ if (reuseaddr) {
+ // set SO_REUSEADDR on the listening socket.
+ int on = 1;
+ int rc;
+ rc = setsockopt(sock,
+ SOL_SOCKET,
+ SO_REUSEADDR,
+ reinterpret_cast<char*>(&on),
+ sizeof(on));
+ if (rc < 0) {
+ close(sock);
+ LOG(FATAL) << "setsockopt() failed fd=" << listen_fd << "\n";
+ }
+ }
+#ifndef SO_REUSEPORT
+#define SO_REUSEPORT 15
+#endif
+ if (reuseport) {
+ // set SO_REUSEADDR on the listening socket.
+ int on = 1;
+ int rc;
+ rc = setsockopt(sock,
+ SOL_SOCKET,
+ SO_REUSEPORT,
+ reinterpret_cast<char*>(&on),
+ sizeof(on));
+ if (rc < 0) {
+ close(sock);
+ LOG(FATAL) << "setsockopt() failed fd=" << listen_fd << "\n";
+ }
+ }
+
+ if (bind(sock, results->ai_addr, results->ai_addrlen)) {
+ // If we are waiting for the interface to be raised, such as in an
+ // HA environment, ignore reporting any errors.
+ int saved_errno = errno;
+ if (!wait_for_iface || errno != EADDRNOTAVAIL) {
+ LOG(ERROR) << "Bind was unsuccessful for (" << host << ":" << port
+ << "): " << strerror(errno) << "\n";
+ }
+ // if we knew that we were not multithreaded, we could do the following:
+ // " : " << strerror(errno) << "\n";
+ if (CloseSocket(&sock, 100)) {
+ if (saved_errno == EADDRNOTAVAIL) {
+ return -3;
+ }
+ return -2;
+ } else {
+ // couldn't even close the dang socket?!
+ LOG(ERROR) << "Unable to close the socket.. Considering this a fatal "
+ "error, and exiting\n";
+ exit(EXIT_FAILURE);
+ return -1;
+ }
+ }
+
+ if (disable_nagle) {
+ if (!SetDisableNagle(sock)) {
+ return -1;
+ }
+ }
+
+ if (listen(sock, backlog)) {
+ // listen was unsuccessful.
+ LOG(ERROR) << "Listen was unsuccessful for (" << host << ":" << port
+ << "): " << strerror(errno) << "\n";
+ // if we knew that we were not multithreaded, we could do the following:
+ // " : " << strerror(errno) << "\n";
+
+ if (CloseSocket(&sock, 100)) {
+ sock = -1;
+ return -1;
+ } else {
+ // couldn't even close the dang socket?!
+ LOG(FATAL) << "Unable to close the socket.. Considering this a fatal "
+ "error, and exiting\n";
+ }
+ }
+
+ // If we've gotten to here, Yeay! Success!
+ *listen_fd = sock;
+
+ return 0;
+}
+
+int CreateConnectedSocket(int* connect_fd,
+ const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ bool disable_nagle) {
+ const char* node = NULL;
+ const char* service = NULL;
+
+ *connect_fd = -1;
+ if (!host.empty())
+ node = host.c_str();
+ if (!port.empty())
+ service = port.c_str();
+
+ struct addrinfo* results = 0;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+
+ if (is_numeric_host_address)
+ hints.ai_flags = AI_NUMERICHOST; // iff you know the name is numeric.
+ hints.ai_flags |= AI_PASSIVE;
+
+ hints.ai_family = PF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int err = 0;
+ if ((err = getaddrinfo(node, service, &hints, &results))) {
+ // gai_strerror -is- threadsafe, so we get to use it here.
+ LOG(ERROR) << "getaddrinfo for (" << node << ":" << service
+ << "): " << gai_strerror(err);
+ return -1;
+ }
+ // this will delete the addrinfo memory when we return from this function.
+ AddrinfoGuard addrinfo_guard(results);
+
+ int sock =
+ socket(results->ai_family, results->ai_socktype, results->ai_protocol);
+ if (sock == -1) {
+ LOG(ERROR) << "Unable to create socket for (" << node << ":" << service
+ << "): " << strerror(errno);
+ return -1;
+ }
+
+ FlipSetNonBlocking(sock);
+
+ if (disable_nagle) {
+ if (!SetDisableNagle(sock)) {
+ return -1;
+ }
+ }
+
+ int ret_val = 0;
+ if (connect(sock, results->ai_addr, results->ai_addrlen)) {
+ if (errno != EINPROGRESS) {
+ LOG(ERROR) << "Connect was unsuccessful for (" << node << ":" << service
+ << "): " << strerror(errno);
+ close(sock);
+ return -1;
+ }
+ } else {
+ ret_val = 1;
+ }
+
+ // If we've gotten to here, Yeay! Success!
+ *connect_fd = sock;
+
+ return ret_val;
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/create_listener.h b/net/tools/flip_server/create_listener.h
new file mode 100644
index 0000000..8ab619c
--- /dev/null
+++ b/net/tools/flip_server/create_listener.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
+#define NET_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
+
+#include <iosfwd>
+#include <string>
+
+namespace net {
+
+void FlipSetNonBlocking(int fd);
+
+// Summary:
+// creates a socket for listening, and bind()s and listen()s it.
+// Args:
+// host - hostname or numeric address, or empty-string if you want
+// to bind to listen on all addresses
+// port - a port number or service name. By service name I mean a
+// -real- service name, not a Google service name. I'd suggest
+// you just stick to a numeric representation like "80"
+// is_numeric_host_address -
+// if you know that the host address has already been looked-up,
+// and will be provided in numeric form like "130.207.244.244",
+// then you can set this to true, and it will save you the time
+// of a DNS lookup.
+// backlog - passed into listen. This is the number of pending incoming
+// connections a socket which is listening may have acquired before
+// the OS starts rejecting new incoming connections.
+// reuseaddr - if true sets SO_REUSEADDR on the listening socket
+// reuseport - if true sets SO_REUSEPORT on the listening socket
+// wait_for_iface - A boolean indicating that CreateListeningSocket should
+// block until the interface that it will bind to has been
+// raised. This is intended for HA environments.
+// disable_nagle - if true sets TCP_NODELAY on the listening socket.
+// listen_fd - this will be assigned a positive value if the socket is
+// successfully created, else it will be assigned -1.
+int CreateListeningSocket(const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ int backlog,
+ bool reuseaddr,
+ bool reuseport,
+ bool wait_for_iface,
+ bool disable_nagle,
+ int* listen_fd);
+
+int CreateConnectedSocket(int* connect_fd,
+ const std::string& host,
+ const std::string& port,
+ bool is_numeric_host_address,
+ bool disable_nagle);
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_CREATE_LISTENER_H__
diff --git a/net/tools/flip_server/flip_config.cc b/net/tools/flip_server/flip_config.cc
new file mode 100644
index 0000000..8be1fe0
--- /dev/null
+++ b/net/tools/flip_server/flip_config.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2011 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.
+
+#include "net/tools/flip_server/flip_config.h"
+
+#include <unistd.h>
+
+namespace net {
+
+FlipAcceptor::FlipAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void* memory_cache)
+ : flip_handler_type_(flip_handler_type),
+ listen_ip_(listen_ip),
+ listen_port_(listen_port),
+ ssl_cert_filename_(ssl_cert_filename),
+ ssl_key_filename_(ssl_key_filename),
+ http_server_ip_(http_server_ip),
+ http_server_port_(http_server_port),
+ https_server_ip_(https_server_ip),
+ https_server_port_(https_server_port),
+ spdy_only_(spdy_only),
+ accept_backlog_size_(accept_backlog_size),
+ disable_nagle_(disable_nagle),
+ accepts_per_wake_(accepts_per_wake),
+ memory_cache_(memory_cache),
+ ssl_session_expiry_(300), // TODO(mbelshe): Hook these up!
+ ssl_disable_compression_(false),
+ idle_socket_timeout_s_(300) {
+ VLOG(1) << "Attempting to listen on " << listen_ip_.c_str() << ":"
+ << listen_port_.c_str();
+ if (!https_server_ip_.size())
+ https_server_ip_ = http_server_ip_;
+ if (!https_server_port_.size())
+ https_server_port_ = http_server_port_;
+
+ while (1) {
+ int ret = CreateListeningSocket(listen_ip_,
+ listen_port_,
+ true,
+ accept_backlog_size_,
+ true,
+ reuseport,
+ wait_for_iface,
+ disable_nagle_,
+ &listen_fd_);
+ if (ret == 0) {
+ break;
+ } else if (ret == -3 && wait_for_iface) {
+ // Binding error EADDRNOTAVAIL was encounted. We need
+ // to wait for the interfaces to raised. try again.
+ usleep(200000);
+ } else {
+ LOG(ERROR) << "Unable to create listening socket for: ret = " << ret
+ << ": " << listen_ip_.c_str() << ":" << listen_port_.c_str();
+ return;
+ }
+ }
+
+ FlipSetNonBlocking(listen_fd_);
+ VLOG(1) << "Listening on socket: ";
+ if (flip_handler_type == FLIP_HANDLER_PROXY)
+ VLOG(1) << "\tType : Proxy";
+ else if (FLIP_HANDLER_SPDY_SERVER)
+ VLOG(1) << "\tType : SPDY Server";
+ else if (FLIP_HANDLER_HTTP_SERVER)
+ VLOG(1) << "\tType : HTTP Server";
+ VLOG(1) << "\tIP : " << listen_ip_;
+ VLOG(1) << "\tPort : " << listen_port_;
+ VLOG(1) << "\tHTTP Server : " << http_server_ip_ << ":" << http_server_port_;
+ VLOG(1) << "\tHTTPS Server : " << https_server_ip_ << ":"
+ << https_server_port_;
+ VLOG(1) << "\tSSL : " << (ssl_cert_filename.size() ? "true"
+ : "false");
+ VLOG(1) << "\tCertificate : " << ssl_cert_filename;
+ VLOG(1) << "\tKey : " << ssl_key_filename;
+ VLOG(1) << "\tSpdy Only : " << (spdy_only ? "true" : "false");
+}
+
+FlipAcceptor::~FlipAcceptor() {}
+
+FlipConfig::FlipConfig()
+ : server_think_time_in_s_(0),
+ log_destination_(logging::LOG_TO_SYSTEM_DEBUG_LOG),
+ wait_for_iface_(false) {}
+
+FlipConfig::~FlipConfig() {}
+
+void FlipConfig::AddAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void* memory_cache) {
+ // TODO(mbelshe): create a struct FlipConfigArgs{} for the arguments.
+ acceptors_.push_back(new FlipAcceptor(flip_handler_type,
+ listen_ip,
+ listen_port,
+ ssl_cert_filename,
+ ssl_key_filename,
+ http_server_ip,
+ http_server_port,
+ https_server_ip,
+ https_server_port,
+ spdy_only,
+ accept_backlog_size,
+ disable_nagle,
+ accepts_per_wake,
+ reuseport,
+ wait_for_iface,
+ memory_cache));
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/flip_config.h b/net/tools/flip_server/flip_config.h
new file mode 100644
index 0000000..90a4199
--- /dev/null
+++ b/net/tools/flip_server/flip_config.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_FLIP_CONFIG_H_
+#define NET_TOOLS_FLIP_SERVER_FLIP_CONFIG_H_
+
+#include <arpa/inet.h>
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "net/tools/flip_server/create_listener.h"
+
+namespace net {
+
+enum FlipHandlerType {
+ FLIP_HANDLER_PROXY,
+ FLIP_HANDLER_SPDY_SERVER,
+ FLIP_HANDLER_HTTP_SERVER
+};
+
+class FlipAcceptor {
+ public:
+ FlipAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void* memory_cache);
+ ~FlipAcceptor();
+
+ enum FlipHandlerType flip_handler_type_;
+ std::string listen_ip_;
+ std::string listen_port_;
+ std::string ssl_cert_filename_;
+ std::string ssl_key_filename_;
+ std::string http_server_ip_;
+ std::string http_server_port_;
+ std::string https_server_ip_;
+ std::string https_server_port_;
+ int spdy_only_;
+ int accept_backlog_size_;
+ bool disable_nagle_;
+ int accepts_per_wake_;
+ int listen_fd_;
+ void* memory_cache_;
+ int ssl_session_expiry_;
+ bool ssl_disable_compression_;
+ int idle_socket_timeout_s_;
+};
+
+class FlipConfig {
+ public:
+ FlipConfig();
+ ~FlipConfig();
+
+ void AddAcceptor(enum FlipHandlerType flip_handler_type,
+ std::string listen_ip,
+ std::string listen_port,
+ std::string ssl_cert_filename,
+ std::string ssl_key_filename,
+ std::string http_server_ip,
+ std::string http_server_port,
+ std::string https_server_ip,
+ std::string https_server_port,
+ int spdy_only,
+ int accept_backlog_size,
+ bool disable_nagle,
+ int accepts_per_wake,
+ bool reuseport,
+ bool wait_for_iface,
+ void* memory_cache);
+
+ std::vector<FlipAcceptor*> acceptors_;
+ double server_think_time_in_s_;
+ enum logging::LoggingDestination log_destination_;
+ std::string log_filename_;
+ bool wait_for_iface_;
+ int ssl_session_expiry_;
+ bool ssl_disable_compression_;
+ int idle_socket_timeout_s_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_FLIP_CONFIG_H_
diff --git a/net/tools/flip_server/flip_in_mem_edsm_server.cc b/net/tools/flip_server/flip_in_mem_edsm_server.cc
new file mode 100644
index 0000000..a8459b6
--- /dev/null
+++ b/net/tools/flip_server/flip_in_mem_edsm_server.cc
@@ -0,0 +1,422 @@
+// Copyright (c) 2011 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.
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/synchronization/lock.h"
+#include "base/timer/timer.h"
+#include "net/tools/balsa/split.h"
+#include "net/tools/flip_server/acceptor_thread.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/output_ordering.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/sm_interface.h"
+#include "net/tools/flip_server/spdy_interface.h"
+#include "net/tools/flip_server/streamer_interface.h"
+
+// If true, then disables the nagle algorithm);
+bool FLAGS_disable_nagle = true;
+
+// The number of times that accept() will be called when the
+// alarm goes off when the accept_using_alarm flag is set to true.
+// If set to 0, accept() will be performed until the accept queue
+// is completely drained and the accept() call returns an error);
+int32 FLAGS_accepts_per_wake = 0;
+
+// The size of the TCP accept backlog);
+int32 FLAGS_accept_backlog_size = 1024;
+
+// If set to false a single socket will be used. If set to true
+// then a new socket will be created for each accept thread.
+// Note that this only works with kernels that support
+// SO_REUSEPORT);
+bool FLAGS_reuseport = false;
+
+// Flag to force spdy, even if NPN is not negotiated.
+bool FLAGS_force_spdy = false;
+
+// The amount of time the server delays before sending back the
+// reply);
+double FLAGS_server_think_time_in_s = 0;
+
+net::FlipConfig g_proxy_config;
+
+std::vector<std::string>& split(const std::string& s,
+ char delim,
+ std::vector<std::string>& elems) {
+ std::stringstream ss(s);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+ return elems;
+}
+
+std::vector<std::string> split(const std::string& s, char delim) {
+ std::vector<std::string> elems;
+ return split(s, delim, elems);
+}
+
+bool GotQuitFromStdin() {
+ // Make stdin nonblocking. Yes this is done each time. Oh well.
+ fcntl(0, F_SETFL, O_NONBLOCK);
+ char c;
+ std::string maybequit;
+ while (read(0, &c, 1) > 0) {
+ maybequit += c;
+ }
+ if (maybequit.size()) {
+ VLOG(1) << "scanning string: \"" << maybequit << "\"";
+ }
+ return (maybequit.size() > 1 &&
+ (maybequit.c_str()[0] == 'q' || maybequit.c_str()[0] == 'Q'));
+}
+
+const char* BoolToStr(bool b) {
+ if (b)
+ return "true";
+ return "false";
+}
+
+static bool wantExit = false;
+static bool wantLogClose = false;
+void SignalHandler(int signum) {
+ switch (signum) {
+ case SIGTERM:
+ case SIGINT:
+ wantExit = true;
+ break;
+ case SIGHUP:
+ wantLogClose = true;
+ break;
+ }
+}
+
+static int OpenPidFile(const char* pidfile) {
+ int fd;
+ struct stat pid_stat;
+ int ret;
+
+ fd = open(pidfile, O_RDWR | O_CREAT, 0600);
+ if (fd == -1) {
+ fprintf(stderr, "Could not open pid file '%s' for reading.\n", pidfile);
+ exit(1);
+ }
+
+ ret = flock(fd, LOCK_EX | LOCK_NB);
+ if (ret == -1) {
+ if (errno == EWOULDBLOCK) {
+ fprintf(stderr, "Flip server is already running.\n");
+ } else {
+ perror("Error getting lock on pid file");
+ }
+ exit(1);
+ }
+
+ if (fstat(fd, &pid_stat) == -1) {
+ fprintf(
+ stderr, "Could not stat pid file '%s': %s\n", pidfile, strerror(errno));
+ exit(1);
+ }
+ if (pid_stat.st_size != 0) {
+ if (ftruncate(fd, pid_stat.st_size) == -1) {
+ fprintf(stderr,
+ "Could not truncate pid file '%s': %s\n",
+ pidfile,
+ strerror(errno));
+ exit(1);
+ }
+ }
+
+ char pid_str[8];
+ snprintf(pid_str, sizeof(pid_str), "%d", getpid());
+ int bytes = static_cast<int>(strlen(pid_str));
+ if (write(fd, pid_str, strlen(pid_str)) != bytes) {
+ perror("Could not write pid file");
+ close(fd);
+ exit(1);
+ }
+
+ return fd;
+}
+
+int main(int argc, char** argv) {
+ unsigned int i = 0;
+ bool wait_for_iface = false;
+ int pidfile_fd;
+
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGTERM, SignalHandler);
+ signal(SIGINT, SignalHandler);
+ signal(SIGHUP, SignalHandler);
+
+ base::CommandLine::Init(argc, argv);
+ base::CommandLine cl(argc, argv);
+
+ if (cl.HasSwitch("help") || argc < 2) {
+ printf("%s <options>\n", argv[0]);
+ printf(" Proxy options:\n");
+ printf(
+ "\t--proxy<1..n>=\"<listen ip>,<listen port>,"
+ "<ssl cert filename>,\n"
+ "\t <ssl key filename>,<http server ip>,"
+ "<http server port>,\n"
+ "\t [https server ip],[https server port],"
+ "<spdy only 0|1>\"\n"
+ "\t * The https server ip and port may be left empty if they are"
+ " the same as\n"
+ "\t the http server fields.\n"
+ "\t * spdy only prevents non-spdy https connections from being"
+ " passed\n"
+ "\t through the proxy listen ip:port.\n"
+ "\t--forward-ip-header=<header name>\n"
+ "\n Server options:\n"
+ "\t--spdy-server=\"<listen ip>,<listen port>,[ssl cert filename],"
+ "\n\t [ssl key filename]\"\n"
+ "\t--http-server=\"<listen ip>,<listen port>,[ssl cert filename],"
+ "\n\t [ssl key filename]\"\n"
+ "\t * Leaving the ssl cert and key fields empty will disable ssl"
+ " for the\n"
+ "\t http and spdy flip servers\n"
+ "\n Global options:\n"
+ "\t--logdest=<file|system|both>\n"
+ "\t--logfile=<logfile>\n"
+ "\t--wait-for-iface\n"
+ "\t * The flip server will block until the listen ip has been"
+ " raised.\n"
+ "\t--ssl-session-expiry=<seconds> (default is 300)\n"
+ "\t--ssl-disable-compression\n"
+ "\t--idle-timeout=<seconds> (default is 300)\n"
+ "\t--pidfile=<filepath> (default /var/run/flip-server.pid)\n"
+ "\t--help\n");
+ exit(0);
+ }
+
+ if (cl.HasSwitch("pidfile")) {
+ pidfile_fd = OpenPidFile(cl.GetSwitchValueASCII("pidfile").c_str());
+ } else {
+ pidfile_fd = OpenPidFile(PIDFILE);
+ }
+
+ net::OutputOrdering::set_server_think_time_in_s(FLAGS_server_think_time_in_s);
+
+ if (cl.HasSwitch("forward-ip-header")) {
+ net::SpdySM::set_forward_ip_header(
+ cl.GetSwitchValueASCII("forward-ip-header"));
+ net::StreamerSM::set_forward_ip_header(
+ cl.GetSwitchValueASCII("forward-ip-header"));
+ }
+
+ if (cl.HasSwitch("logdest")) {
+ std::string log_dest_value = cl.GetSwitchValueASCII("logdest");
+ if (log_dest_value.compare("file") == 0) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_FILE;
+ } else if (log_dest_value.compare("system") == 0) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ } else if (log_dest_value.compare("both") == 0) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_ALL;
+ } else {
+ LOG(FATAL) << "Invalid logging destination value: " << log_dest_value;
+ }
+ } else {
+ g_proxy_config.log_destination_ = logging::LOG_NONE;
+ }
+
+ if (cl.HasSwitch("logfile")) {
+ g_proxy_config.log_filename_ = cl.GetSwitchValueASCII("logfile");
+ if (g_proxy_config.log_destination_ == logging::LOG_NONE) {
+ g_proxy_config.log_destination_ = logging::LOG_TO_FILE;
+ }
+ } else if ((g_proxy_config.log_destination_ & logging::LOG_TO_FILE) != 0) {
+ LOG(FATAL) << "Logging destination requires a log file to be specified.";
+ }
+
+ if (cl.HasSwitch("wait-for-iface")) {
+ wait_for_iface = true;
+ }
+
+ if (cl.HasSwitch("ssl-session-expiry")) {
+ std::string session_expiry = cl.GetSwitchValueASCII("ssl-session-expiry");
+ g_proxy_config.ssl_session_expiry_ = atoi(session_expiry.c_str());
+ }
+
+ if (cl.HasSwitch("ssl-disable-compression")) {
+ g_proxy_config.ssl_disable_compression_ = true;
+ }
+
+ if (cl.HasSwitch("idle-timeout")) {
+ g_proxy_config.idle_socket_timeout_s_ =
+ atoi(cl.GetSwitchValueASCII("idle-timeout").c_str());
+ }
+
+ if (cl.HasSwitch("force_spdy"))
+ net::SMConnection::set_force_spdy(true);
+
+ logging::LoggingSettings settings;
+ settings.logging_dest = g_proxy_config.log_destination_;
+ settings.log_file = g_proxy_config.log_filename_.c_str();
+ settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ logging::InitLogging(settings);
+
+ LOG(INFO) << "Flip SPDY proxy started with configuration:";
+ LOG(INFO) << "Logging destination : " << g_proxy_config.log_destination_;
+ LOG(INFO) << "Log file : " << g_proxy_config.log_filename_;
+ LOG(INFO) << "Forward IP Header : "
+ << (net::SpdySM::forward_ip_header().length()
+ ? net::SpdySM::forward_ip_header()
+ : "<disabled>");
+ LOG(INFO) << "Wait for interfaces : " << (wait_for_iface ? "true"
+ : "false");
+ LOG(INFO) << "Accept backlog size : " << FLAGS_accept_backlog_size;
+ LOG(INFO) << "Accepts per wake : " << FLAGS_accepts_per_wake;
+ LOG(INFO) << "Disable nagle : " << (FLAGS_disable_nagle ? "true"
+ : "false");
+ LOG(INFO) << "Reuseport : " << (FLAGS_reuseport ? "true"
+ : "false");
+ LOG(INFO) << "Force SPDY : " << (FLAGS_force_spdy ? "true"
+ : "false");
+ LOG(INFO) << "SSL session expiry : "
+ << g_proxy_config.ssl_session_expiry_;
+ LOG(INFO) << "SSL disable compression : "
+ << g_proxy_config.ssl_disable_compression_;
+ LOG(INFO) << "Connection idle timeout : "
+ << g_proxy_config.idle_socket_timeout_s_;
+
+ // Proxy Acceptors
+ while (true) {
+ i += 1;
+ std::stringstream name;
+ name << "proxy" << i;
+ if (!cl.HasSwitch(name.str())) {
+ break;
+ }
+ std::string value = cl.GetSwitchValueASCII(name.str());
+ std::vector<std::string> valueArgs = split(value, ',');
+ CHECK_EQ((unsigned int)9, valueArgs.size());
+ int spdy_only = atoi(valueArgs[8].c_str());
+ // If wait_for_iface is enabled, then this call will block
+ // indefinitely until the interface is raised.
+ g_proxy_config.AddAcceptor(net::FLIP_HANDLER_PROXY,
+ valueArgs[0],
+ valueArgs[1],
+ valueArgs[2],
+ valueArgs[3],
+ valueArgs[4],
+ valueArgs[5],
+ valueArgs[6],
+ valueArgs[7],
+ spdy_only,
+ FLAGS_accept_backlog_size,
+ FLAGS_disable_nagle,
+ FLAGS_accepts_per_wake,
+ FLAGS_reuseport,
+ wait_for_iface,
+ NULL);
+ }
+
+ // Spdy Server Acceptor
+ net::MemoryCache spdy_memory_cache;
+ if (cl.HasSwitch("spdy-server")) {
+ spdy_memory_cache.AddFiles();
+ std::string value = cl.GetSwitchValueASCII("spdy-server");
+ std::vector<std::string> valueArgs = split(value, ',');
+ while (valueArgs.size() < 4)
+ valueArgs.push_back(std::string());
+ g_proxy_config.AddAcceptor(net::FLIP_HANDLER_SPDY_SERVER,
+ valueArgs[0],
+ valueArgs[1],
+ valueArgs[2],
+ valueArgs[3],
+ std::string(),
+ std::string(),
+ std::string(),
+ std::string(),
+ 0,
+ FLAGS_accept_backlog_size,
+ FLAGS_disable_nagle,
+ FLAGS_accepts_per_wake,
+ FLAGS_reuseport,
+ wait_for_iface,
+ &spdy_memory_cache);
+ }
+
+ // Spdy Server Acceptor
+ net::MemoryCache http_memory_cache;
+ if (cl.HasSwitch("http-server")) {
+ http_memory_cache.AddFiles();
+ std::string value = cl.GetSwitchValueASCII("http-server");
+ std::vector<std::string> valueArgs = split(value, ',');
+ while (valueArgs.size() < 4)
+ valueArgs.push_back(std::string());
+ g_proxy_config.AddAcceptor(net::FLIP_HANDLER_HTTP_SERVER,
+ valueArgs[0],
+ valueArgs[1],
+ valueArgs[2],
+ valueArgs[3],
+ std::string(),
+ std::string(),
+ std::string(),
+ std::string(),
+ 0,
+ FLAGS_accept_backlog_size,
+ FLAGS_disable_nagle,
+ FLAGS_accepts_per_wake,
+ FLAGS_reuseport,
+ wait_for_iface,
+ &http_memory_cache);
+ }
+
+ std::vector<net::SMAcceptorThread*> sm_worker_threads_;
+
+ for (i = 0; i < g_proxy_config.acceptors_.size(); i++) {
+ net::FlipAcceptor* acceptor = g_proxy_config.acceptors_[i];
+
+ sm_worker_threads_.push_back(new net::SMAcceptorThread(
+ acceptor, (net::MemoryCache*)acceptor->memory_cache_));
+ // Note that spdy_memory_cache is not threadsafe, it is merely
+ // thread compatible. Thus, if ever we are to spawn multiple threads,
+ // we either must make the MemoryCache threadsafe, or use
+ // a separate MemoryCache for each thread.
+ //
+ // The latter is what is currently being done as we spawn
+ // a separate thread for each http and spdy server acceptor.
+
+ sm_worker_threads_.back()->InitWorker();
+ sm_worker_threads_.back()->Start();
+ }
+
+ while (!wantExit) {
+ // Close logfile when HUP signal is received. Logging system will
+ // automatically reopen on next log message.
+ if (wantLogClose) {
+ wantLogClose = false;
+ VLOG(1) << "HUP received, reopening log file.";
+ logging::CloseLogFile();
+ }
+ if (GotQuitFromStdin()) {
+ for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) {
+ sm_worker_threads_[i]->Quit();
+ }
+ for (unsigned int i = 0; i < sm_worker_threads_.size(); ++i) {
+ sm_worker_threads_[i]->Join();
+ }
+ break;
+ }
+ usleep(1000 * 10); // 10 ms
+ }
+
+ unlink(PIDFILE);
+ close(pidfile_fd);
+ return 0;
+}
diff --git a/net/tools/flip_server/flip_test_utils.cc b/net/tools/flip_server/flip_test_utils.cc
new file mode 100644
index 0000000..c99d7d6
--- /dev/null
+++ b/net/tools/flip_server/flip_test_utils.cc
@@ -0,0 +1,13 @@
+// Copyright 2013 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.
+
+#include "net/tools/flip_server/flip_test_utils.h"
+
+namespace net {
+
+MockSMInterface::MockSMInterface() {}
+
+MockSMInterface::~MockSMInterface() {}
+
+} // namespace net
diff --git a/net/tools/flip_server/flip_test_utils.h b/net/tools/flip_server/flip_test_utils.h
new file mode 100644
index 0000000..0a61a60
--- /dev/null
+++ b/net/tools/flip_server/flip_test_utils.h
@@ -0,0 +1,54 @@
+// Copyright 2013 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_TOOLS_FLIP_SERVER_FLIP_TEST_UTILS_H_
+#define NET_TOOLS_FLIP_SERVER_FLIP_TEST_UTILS_H_
+
+#include <string>
+
+#include "net/tools/flip_server/sm_interface.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+class MockSMInterface : public SMInterface {
+ public:
+ MockSMInterface();
+ virtual ~MockSMInterface();
+
+ MOCK_METHOD2(InitSMInterface, void(SMInterface*, int32));
+ MOCK_METHOD8(InitSMConnection,
+ void(SMConnectionPoolInterface*,
+ SMInterface*,
+ EpollServer*,
+ int,
+ std::string,
+ std::string,
+ std::string,
+ bool));
+ MOCK_METHOD2(ProcessReadInput, size_t(const char*, size_t));
+ MOCK_METHOD2(ProcessWriteInput, size_t(const char*, size_t));
+ MOCK_METHOD1(SetStreamID, void(uint32 stream_id));
+ MOCK_CONST_METHOD0(MessageFullyRead, bool());
+ MOCK_CONST_METHOD0(Error, bool());
+ MOCK_CONST_METHOD0(ErrorAsString, const char*());
+ MOCK_METHOD0(Reset, void());
+ MOCK_METHOD1(ResetForNewInterface, void(int32 server_idx));
+ MOCK_METHOD0(ResetForNewConnection, void());
+ MOCK_METHOD0(Cleanup, void());
+ MOCK_METHOD0(PostAcceptHook, int());
+ MOCK_METHOD3(NewStream, void(uint32, uint32, const std::string&));
+ MOCK_METHOD1(SendEOF, void(uint32 stream_id));
+ MOCK_METHOD1(SendErrorNotFound, void(uint32 stream_id));
+ MOCK_METHOD2(SendSynStream, size_t(uint32, const BalsaHeaders&));
+ MOCK_METHOD2(SendSynReply, size_t(uint32, const BalsaHeaders&));
+ MOCK_METHOD5(SendDataFrame, void(uint32, const char*, int64, uint32, bool));
+ MOCK_METHOD0(GetOutput, void());
+ MOCK_METHOD0(set_is_request, void());
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_FLIP_TEST_UTILS_H_
diff --git a/net/tools/flip_server/http_interface.cc b/net/tools/flip_server/http_interface.cc
new file mode 100644
index 0000000..b939c5f
--- /dev/null
+++ b/net/tools/flip_server/http_interface.cc
@@ -0,0 +1,340 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/http_interface.h"
+
+#include "net/tools/balsa/balsa_frame.h"
+#include "net/tools/dump_cache/url_utilities.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/spdy_util.h"
+
+namespace net {
+
+HttpSM::HttpSM(SMConnection* connection,
+ SMInterface* sm_spdy_interface,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor)
+ : http_framer_(new BalsaFrame),
+ stream_id_(0),
+ server_idx_(-1),
+ connection_(connection),
+ sm_spdy_interface_(sm_spdy_interface),
+ output_list_(connection->output_list()),
+ output_ordering_(connection),
+ memory_cache_(connection->memory_cache()),
+ acceptor_(acceptor) {
+ http_framer_->set_balsa_visitor(this);
+ http_framer_->set_balsa_headers(&headers_);
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
+ http_framer_->set_is_request(false);
+}
+HttpSM::~HttpSM() {
+ Reset();
+ delete http_framer_;
+}
+
+void HttpSM::ProcessBodyData(const char* input, size_t size) {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Process Body Data: stream "
+ << stream_id_ << ": size " << size;
+ sm_spdy_interface_->SendDataFrame(stream_id_, input, size, 0, false);
+ }
+}
+
+void HttpSM::ProcessHeaders(const BalsaHeaders& headers) {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER) {
+ std::string host =
+ UrlUtilities::GetUrlHost(headers.GetHeader("Host").as_string());
+ std::string method = headers.request_method().as_string();
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT
+ << "Received Request: " << headers.request_uri().as_string() << " "
+ << method;
+ std::string filename =
+ EncodeURL(headers.request_uri().as_string(), host, method);
+ NewStream(stream_id_, 0, filename);
+ stream_id_ += 2;
+ } else {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Received Response from "
+ << connection_->server_ip_ << ":" << connection_->server_port_
+ << " ";
+ sm_spdy_interface_->SendSynReply(stream_id_, headers);
+ }
+}
+
+void HttpSM::MessageDone() {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: MessageDone. Sending EOF: "
+ << "stream " << stream_id_;
+ sm_spdy_interface_->SendEOF(stream_id_);
+ } else {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: MessageDone.";
+ }
+}
+
+void HttpSM::HandleHeaderError(BalsaFrame* framer) { HandleError(); }
+
+void HttpSM::HandleChunkingError(BalsaFrame* framer) { HandleError(); }
+
+void HttpSM::HandleBodyError(BalsaFrame* framer) { HandleError(); }
+
+void HttpSM::HandleError() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Error detected";
+}
+
+void HttpSM::AddToOutputOrder(const MemCacheIter& mci) {
+ output_ordering_.AddToOutputOrder(mci);
+}
+
+void HttpSM::InitSMInterface(SMInterface* sm_spdy_interface, int32 server_idx) {
+ sm_spdy_interface_ = sm_spdy_interface;
+ server_idx_ = server_idx;
+}
+
+void HttpSM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Initializing server "
+ << "connection.";
+ connection_->InitSMConnection(connection_pool,
+ sm_interface,
+ epoll_server,
+ fd,
+ server_ip,
+ server_port,
+ remote_ip,
+ use_ssl);
+}
+
+size_t HttpSM::ProcessReadInput(const char* data, size_t len) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Process read input: stream "
+ << stream_id_;
+ return http_framer_->ProcessInput(data, len);
+}
+
+size_t HttpSM::ProcessWriteInput(const char* data, size_t len) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Process write input: size "
+ << len << ": stream " << stream_id_;
+ char* dataPtr = new char[len];
+ memcpy(dataPtr, data, len);
+ DataFrame* data_frame = new DataFrame;
+ data_frame->data = dataPtr;
+ data_frame->size = len;
+ data_frame->delete_when_done = true;
+ connection_->EnqueueDataFrame(data_frame);
+ return len;
+}
+
+bool HttpSM::MessageFullyRead() const {
+ return http_framer_->MessageFullyRead();
+}
+
+void HttpSM::SetStreamID(uint32 stream_id) { stream_id_ = stream_id; }
+
+bool HttpSM::Error() const { return http_framer_->Error(); }
+
+const char* HttpSM::ErrorAsString() const {
+ return BalsaFrameEnums::ErrorCodeToString(http_framer_->ErrorCode());
+}
+
+void HttpSM::Reset() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Reset: stream " << stream_id_;
+ http_framer_->Reset();
+}
+
+void HttpSM::ResetForNewConnection() {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Server connection closing "
+ << "to: " << connection_->server_ip_ << ":"
+ << connection_->server_port_ << " ";
+ }
+ // Message has not been fully read, either it is incomplete or the
+ // server is closing the connection to signal message end.
+ if (!MessageFullyRead()) {
+ VLOG(2) << "HTTP response closed before end of file detected. "
+ << "Sending EOF to spdy.";
+ sm_spdy_interface_->SendEOF(stream_id_);
+ }
+ output_ordering_.Reset();
+ http_framer_->Reset();
+ if (sm_spdy_interface_) {
+ sm_spdy_interface_->ResetForNewInterface(server_idx_);
+ }
+}
+
+void HttpSM::Cleanup() {
+ if (!(acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER)) {
+ VLOG(2) << "HttpSM Request Fully Read; stream_id: " << stream_id_;
+ connection_->Cleanup("request complete");
+ }
+}
+
+int HttpSM::PostAcceptHook() { return 1; }
+
+void HttpSM::NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) {
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+ mci.priority = priority;
+ if (!memory_cache_->AssignFileData(filename, &mci)) {
+ // error creating new stream.
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound";
+ SendErrorNotFound(stream_id);
+ } else {
+ AddToOutputOrder(mci);
+ }
+}
+
+void HttpSM::SendEOF(uint32 stream_id) {
+ SendEOFImpl(stream_id);
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ sm_spdy_interface_->ResetForNewInterface(server_idx_);
+ }
+}
+
+void HttpSM::SendErrorNotFound(uint32 stream_id) {
+ SendErrorNotFoundImpl(stream_id);
+}
+
+size_t HttpSM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+}
+
+size_t HttpSM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
+ return SendSynReplyImpl(stream_id, headers);
+}
+
+void HttpSM::SendDataFrame(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) {
+ SendDataFrameImpl(stream_id, data, len, flags, compress);
+}
+
+void HttpSM::SendEOFImpl(uint32 stream_id) {
+ DataFrame* df = new DataFrame;
+ df->data = "0\r\n\r\n";
+ df->size = 5;
+ df->delete_when_done = false;
+ EnqueueDataFrame(df);
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_HTTP_SERVER) {
+ Reset();
+ }
+}
+
+void HttpSM::SendErrorNotFoundImpl(uint32 stream_id) {
+ BalsaHeaders my_headers;
+ my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
+ my_headers.RemoveAllOfHeader("content-length");
+ my_headers.AppendHeader("transfer-encoding", "chunked");
+ SendSynReplyImpl(stream_id, my_headers);
+ SendDataFrame(stream_id, "page not found", 14, 0, false);
+ SendEOFImpl(stream_id);
+ output_ordering_.RemoveStreamId(stream_id);
+}
+
+size_t HttpSM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ SimpleBuffer sb;
+ headers.WriteHeaderAndEndingToBuffer(&sb);
+ DataFrame* df = new DataFrame;
+ df->size = sb.ReadableBytes();
+ char* buffer = new char[df->size];
+ df->data = buffer;
+ df->delete_when_done = true;
+ sb.Read(buffer, df->size);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Sending HTTP Reply header "
+ << stream_id_;
+ size_t df_size = df->size;
+ EnqueueDataFrame(df);
+ return df_size;
+}
+
+size_t HttpSM::SendSynStreamImpl(uint32 stream_id,
+ const BalsaHeaders& headers) {
+ SimpleBuffer sb;
+ headers.WriteHeaderAndEndingToBuffer(&sb);
+ DataFrame* df = new DataFrame;
+ df->size = sb.ReadableBytes();
+ char* buffer = new char[df->size];
+ df->data = buffer;
+ df->delete_when_done = true;
+ sb.Read(buffer, df->size);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Sending HTTP Reply header "
+ << stream_id_;
+ size_t df_size = df->size;
+ EnqueueDataFrame(df);
+ return df_size;
+}
+
+void HttpSM::SendDataFrameImpl(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) {
+ char chunk_buf[128];
+ snprintf(chunk_buf, sizeof(chunk_buf), "%x\r\n", (unsigned int)len);
+ std::string chunk_description(chunk_buf);
+ DataFrame* df = new DataFrame;
+ df->size = chunk_description.size() + len + 2;
+ char* buffer = new char[df->size];
+ df->data = buffer;
+ df->delete_when_done = true;
+ memcpy(buffer, chunk_description.data(), chunk_description.size());
+ memcpy(buffer + chunk_description.size(), data, len);
+ memcpy(buffer + chunk_description.size() + len, "\r\n", 2);
+ EnqueueDataFrame(df);
+}
+
+void HttpSM::EnqueueDataFrame(DataFrame* df) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: Enqueue data frame: stream "
+ << stream_id_;
+ connection_->EnqueueDataFrame(df);
+}
+
+void HttpSM::GetOutput() {
+ MemCacheIter* mci = output_ordering_.GetIter();
+ if (mci == NULL) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: GetOutput: nothing to "
+ << "output!?: stream " << stream_id_;
+ return;
+ }
+ if (!mci->transformed_header) {
+ mci->bytes_sent =
+ SendSynReply(mci->stream_id, *(mci->file_data->headers()));
+ mci->transformed_header = true;
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: GetOutput transformed "
+ << "header stream_id: [" << mci->stream_id << "]";
+ return;
+ }
+ if (mci->body_bytes_consumed >= mci->file_data->body().size()) {
+ SendEOF(mci->stream_id);
+ output_ordering_.RemoveStreamId(mci->stream_id);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "GetOutput remove_stream_id: ["
+ << mci->stream_id << "]";
+ return;
+ }
+ size_t num_to_write =
+ mci->file_data->body().size() - mci->body_bytes_consumed;
+ if (num_to_write > mci->max_segment_size)
+ num_to_write = mci->max_segment_size;
+
+ SendDataFrame(mci->stream_id,
+ mci->file_data->body().data() + mci->body_bytes_consumed,
+ num_to_write,
+ 0,
+ true);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpSM: GetOutput SendDataFrame["
+ << mci->stream_id << "]: " << num_to_write;
+ mci->body_bytes_consumed += num_to_write;
+ mci->bytes_sent += num_to_write;
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/http_interface.h b/net/tools/flip_server/http_interface.h
new file mode 100644
index 0000000..30b7979
--- /dev/null
+++ b/net/tools/flip_server/http_interface.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_HTTP_INTERFACE_H_
+#define NET_TOOLS_FLIP_SERVER_HTTP_INTERFACE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/tools/balsa/balsa_headers.h"
+#include "net/tools/balsa/balsa_visitor_interface.h"
+#include "net/tools/flip_server/output_ordering.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/sm_interface.h"
+
+namespace net {
+
+class BalsaFrame;
+class DataFrame;
+class EpollServer;
+class FlipAcceptor;
+class MemoryCache;
+
+class HttpSM : public BalsaVisitorInterface, public SMInterface {
+ public:
+ HttpSM(SMConnection* connection,
+ SMInterface* sm_spdy_interface,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor);
+ virtual ~HttpSM();
+
+ private:
+ // BalsaVisitorInterface:
+ virtual void ProcessBodyInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE;
+ virtual void ProcessHeaderInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaders(const BalsaHeaders& headers) OVERRIDE;
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(const char* line_input,
+ size_t line_length,
+ const char* version_input,
+ size_t version_length,
+ const char* status_input,
+ size_t status_length,
+ const char* reason_input,
+ size_t reason_length) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t chunk_length) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char* input, size_t size) OVERRIDE {
+ }
+ virtual void HeaderDone() OVERRIDE {}
+ virtual void MessageDone() OVERRIDE;
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {}
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE;
+
+ void HandleError();
+
+ public:
+ void AddToOutputOrder(const MemCacheIter& mci);
+ BalsaFrame* spdy_framer() { return http_framer_; }
+ virtual void set_is_request() OVERRIDE {}
+ const OutputOrdering& output_ordering() const { return output_ordering_; }
+
+ // SMInterface:
+ virtual void InitSMInterface(SMInterface* sm_spdy_interface,
+ int32 server_idx) OVERRIDE;
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) OVERRIDE;
+ virtual size_t ProcessReadInput(const char* data, size_t len) OVERRIDE;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) OVERRIDE;
+ virtual bool MessageFullyRead() const OVERRIDE;
+ virtual void SetStreamID(uint32 stream_id) OVERRIDE;
+ virtual bool Error() const OVERRIDE;
+ virtual const char* ErrorAsString() const OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual void ResetForNewInterface(int32 server_idx) OVERRIDE {}
+ virtual void ResetForNewConnection() OVERRIDE;
+ virtual void Cleanup() OVERRIDE;
+ virtual int PostAcceptHook() OVERRIDE;
+
+ virtual void NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) OVERRIDE;
+ virtual void SendEOF(uint32 stream_id) OVERRIDE;
+ virtual void SendErrorNotFound(uint32 stream_id) OVERRIDE;
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual void SendDataFrame(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) OVERRIDE;
+
+ private:
+ void SendEOFImpl(uint32 stream_id);
+ void SendErrorNotFoundImpl(uint32 stream_id);
+ void SendOKResponseImpl(uint32 stream_id, const std::string& output);
+ size_t SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers);
+ size_t SendSynStreamImpl(uint32 stream_id, const BalsaHeaders& headers);
+ void SendDataFrameImpl(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress);
+ void EnqueueDataFrame(DataFrame* df);
+ virtual void GetOutput() OVERRIDE;
+
+ private:
+ BalsaFrame* http_framer_;
+ BalsaHeaders headers_;
+ uint32 stream_id_;
+ int32 server_idx_;
+
+ SMConnection* connection_;
+ SMInterface* sm_spdy_interface_;
+ OutputList* output_list_;
+ OutputOrdering output_ordering_;
+ MemoryCache* memory_cache_;
+ FlipAcceptor* acceptor_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_HTTP_INTERFACE_H_
diff --git a/net/tools/flip_server/http_interface_test.cc b/net/tools/flip_server/http_interface_test.cc
new file mode 100644
index 0000000..969607b
--- /dev/null
+++ b/net/tools/flip_server/http_interface_test.cc
@@ -0,0 +1,491 @@
+// Copyright 2013 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.
+
+#include "net/tools/flip_server/http_interface.h"
+
+#include <list>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "base/strings/string_piece.h"
+#include "net/tools/balsa/balsa_enums.h"
+#include "net/tools/balsa/balsa_frame.h"
+#include "net/tools/balsa/balsa_headers.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/flip_test_utils.h"
+#include "net/tools/flip_server/mem_cache.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+using ::base::StringPiece;
+using ::testing::_;
+using ::testing::InSequence;
+
+namespace {
+
+class MockSMConnection : public SMConnection {
+ public:
+ MockSMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix)
+ : SMConnection(epoll_server,
+ ssl_state,
+ memory_cache,
+ acceptor,
+ log_prefix) {}
+
+ MOCK_METHOD0(Cleanup, void());
+ MOCK_METHOD8(InitSMConnection,
+ void(SMConnectionPoolInterface*,
+ SMInterface*,
+ EpollServer*,
+ int,
+ std::string,
+ std::string,
+ std::string,
+ bool));
+};
+
+class FlipHttpSMTest : public ::testing::Test {
+ public:
+ explicit FlipHttpSMTest(FlipHandlerType type = FLIP_HANDLER_PROXY) {
+ SSLState* ssl_state = NULL;
+ mock_another_interface_.reset(new MockSMInterface);
+ memory_cache_.reset(new MemoryCache);
+ acceptor_.reset(new FlipAcceptor(type,
+ "127.0.0.1",
+ "8941",
+ "ssl_cert_filename",
+ "ssl_key_filename",
+ "127.0.0.1",
+ "8942",
+ "127.0.0.1",
+ "8943",
+ 1,
+ 0,
+ true,
+ 1,
+ false,
+ true,
+ NULL));
+ epoll_server_.reset(new EpollServer);
+ connection_.reset(new MockSMConnection(epoll_server_.get(),
+ ssl_state,
+ memory_cache_.get(),
+ acceptor_.get(),
+ "log_prefix"));
+
+ interface_.reset(new HttpSM(connection_.get(),
+ mock_another_interface_.get(),
+ memory_cache_.get(),
+ acceptor_.get()));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (acceptor_->listen_fd_ >= 0) {
+ epoll_server_->UnregisterFD(acceptor_->listen_fd_);
+ close(acceptor_->listen_fd_);
+ acceptor_->listen_fd_ = -1;
+ }
+ STLDeleteElements(connection_->output_list());
+ }
+
+ bool HasStream(uint32 stream_id) {
+ return interface_->output_ordering().ExistsInPriorityMaps(stream_id);
+ }
+
+ protected:
+ scoped_ptr<MockSMInterface> mock_another_interface_;
+ scoped_ptr<MemoryCache> memory_cache_;
+ scoped_ptr<FlipAcceptor> acceptor_;
+ scoped_ptr<EpollServer> epoll_server_;
+ scoped_ptr<MockSMConnection> connection_;
+ scoped_ptr<HttpSM> interface_;
+};
+
+class FlipHttpSMProxyTest : public FlipHttpSMTest {
+ public:
+ FlipHttpSMProxyTest() : FlipHttpSMTest(FLIP_HANDLER_PROXY) {}
+ virtual ~FlipHttpSMProxyTest() {}
+};
+
+class FlipHttpSMHttpTest : public FlipHttpSMTest {
+ public:
+ FlipHttpSMHttpTest() : FlipHttpSMTest(FLIP_HANDLER_HTTP_SERVER) {}
+ virtual ~FlipHttpSMHttpTest() {}
+};
+
+class FlipHttpSMSpdyTest : public FlipHttpSMTest {
+ public:
+ FlipHttpSMSpdyTest() : FlipHttpSMTest(FLIP_HANDLER_SPDY_SERVER) {}
+ virtual ~FlipHttpSMSpdyTest() {}
+};
+
+TEST_F(FlipHttpSMTest, Construct) {
+ ASSERT_FALSE(interface_->spdy_framer()->is_request());
+}
+
+TEST_F(FlipHttpSMTest, AddToOutputOrder) {
+ uint32 stream_id = 13;
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+
+ {
+ BalsaHeaders headers;
+ std::string filename = "foobar";
+ memory_cache_->InsertFile(&headers, filename, "");
+ mci.file_data = memory_cache_->GetFileData(filename);
+ }
+
+ interface_->AddToOutputOrder(mci);
+ ASSERT_TRUE(HasStream(stream_id));
+}
+
+TEST_F(FlipHttpSMTest, InitSMInterface) {
+ scoped_ptr<MockSMInterface> mock(new MockSMInterface);
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendEOF(_));
+ EXPECT_CALL(*mock_another_interface_, ResetForNewInterface(_));
+ EXPECT_CALL(*mock, SendEOF(_));
+ EXPECT_CALL(*mock, ResetForNewInterface(_));
+ }
+
+ interface_->ResetForNewConnection();
+ interface_->InitSMInterface(mock.get(), 0);
+ interface_->ResetForNewConnection();
+}
+
+TEST_F(FlipHttpSMTest, InitSMConnection) {
+ EXPECT_CALL(*connection_, InitSMConnection(_, _, _, _, _, _, _, _));
+
+ interface_->InitSMConnection(NULL, NULL, NULL, 0, "", "", "", false);
+}
+
+TEST_F(FlipHttpSMTest, ProcessReadInput) {
+ std::string data =
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: 14\r\n\r\n"
+ "hello, world\r\n";
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendSynReply(_, _));
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*mock_another_interface_, SendDataFrame(_, _, _, _, _));
+ EXPECT_CALL(*mock_another_interface_, SendEOF(_));
+ }
+
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ interface_->spdy_framer()->ParseState());
+
+ size_t read = interface_->ProcessReadInput(data.data(), data.size());
+ ASSERT_EQ(39u, read);
+ checkpoint.Call(0);
+ read += interface_->ProcessReadInput(&data.data()[read], data.size() - read);
+ ASSERT_EQ(data.size(), read);
+ ASSERT_EQ(BalsaFrameEnums::MESSAGE_FULLY_READ,
+ interface_->spdy_framer()->ParseState());
+ ASSERT_TRUE(interface_->MessageFullyRead());
+}
+
+TEST_F(FlipHttpSMTest, ProcessWriteInput) {
+ std::string data = "hello, world";
+ interface_->ProcessWriteInput(data.data(), data.size());
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ ASSERT_EQ(data, StringPiece(df->data, df->size));
+ ASSERT_EQ(connection_->output_list()->end(), i);
+}
+
+TEST_F(FlipHttpSMTest, Reset) {
+ std::string data = "HTTP/1.1 200 OK\r\n\r\n";
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendSynReply(_, _));
+ EXPECT_CALL(checkpoint, Call(0));
+ }
+
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ interface_->spdy_framer()->ParseState());
+
+ interface_->ProcessReadInput(data.data(), data.size());
+ checkpoint.Call(0);
+ ASSERT_FALSE(interface_->MessageFullyRead());
+ ASSERT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE,
+ interface_->spdy_framer()->ParseState());
+
+ interface_->Reset();
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ interface_->spdy_framer()->ParseState());
+}
+
+TEST_F(FlipHttpSMTest, ResetForNewConnection) {
+ std::string data = "HTTP/1.1 200 OK\r\n\r\n";
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendSynReply(_, _));
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*mock_another_interface_, SendEOF(_));
+ EXPECT_CALL(*mock_another_interface_, ResetForNewInterface(_));
+ }
+
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ interface_->spdy_framer()->ParseState());
+
+ interface_->ProcessReadInput(data.data(), data.size());
+ checkpoint.Call(0);
+ ASSERT_FALSE(interface_->MessageFullyRead());
+ ASSERT_EQ(BalsaFrameEnums::READING_UNTIL_CLOSE,
+ interface_->spdy_framer()->ParseState());
+
+ interface_->ResetForNewConnection();
+ ASSERT_EQ(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE,
+ interface_->spdy_framer()->ParseState());
+}
+
+TEST_F(FlipHttpSMTest, NewStream) {
+ uint32 stream_id = 4;
+ {
+ BalsaHeaders headers;
+ std::string filename = "foobar";
+ memory_cache_->InsertFile(&headers, filename, "");
+ }
+
+ interface_->NewStream(stream_id, 1, "foobar");
+ ASSERT_TRUE(HasStream(stream_id));
+}
+
+TEST_F(FlipHttpSMTest, NewStreamError) {
+ std::string syn_reply =
+ "HTTP/1.1 404 Not Found\r\n"
+ "transfer-encoding: chunked\r\n\r\n";
+ std::string body = "e\r\npage not found\r\n";
+ uint32 stream_id = 4;
+
+ ASSERT_FALSE(HasStream(stream_id));
+ interface_->NewStream(stream_id, 1, "foobar");
+
+ ASSERT_EQ(3u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ ASSERT_EQ(syn_reply, StringPiece(df->data, df->size));
+ df = *i++;
+ ASSERT_EQ(body, StringPiece(df->data, df->size));
+ df = *i++;
+ ASSERT_EQ("0\r\n\r\n", StringPiece(df->data, df->size));
+ ASSERT_FALSE(HasStream(stream_id));
+}
+
+TEST_F(FlipHttpSMTest, SendErrorNotFound) {
+ std::string syn_reply =
+ "HTTP/1.1 404 Not Found\r\n"
+ "transfer-encoding: chunked\r\n\r\n";
+ std::string body = "e\r\npage not found\r\n";
+ uint32 stream_id = 13;
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+
+ {
+ BalsaHeaders headers;
+ std::string filename = "foobar";
+ memory_cache_->InsertFile(&headers, filename, "");
+ mci.file_data = memory_cache_->GetFileData(filename);
+ }
+
+ interface_->AddToOutputOrder(mci);
+ ASSERT_TRUE(HasStream(stream_id));
+ interface_->SendErrorNotFound(stream_id);
+
+ ASSERT_EQ(3u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ ASSERT_EQ(syn_reply, StringPiece(df->data, df->size));
+ df = *i++;
+ ASSERT_EQ(body, StringPiece(df->data, df->size));
+ df = *i++;
+ ASSERT_EQ("0\r\n\r\n", StringPiece(df->data, df->size));
+ ASSERT_FALSE(HasStream(stream_id));
+}
+
+TEST_F(FlipHttpSMTest, SendSynStream) {
+ std::string expected =
+ "GET / HTTP/1.0\r\n"
+ "key1: value1\r\n\r\n";
+ BalsaHeaders headers;
+ headers.SetResponseFirstlineFromStringPieces("GET", "/path", "HTTP/1.0");
+ headers.AppendHeader("key1", "value1");
+ interface_->SendSynStream(18, headers);
+
+ // TODO(yhirano): Is this behavior correct?
+ ASSERT_EQ(0u, connection_->output_list()->size());
+}
+
+TEST_F(FlipHttpSMTest, SendSynReply) {
+ std::string expected =
+ "HTTP/1.1 200 OK\r\n"
+ "key1: value1\r\n\r\n";
+ BalsaHeaders headers;
+ headers.SetResponseFirstlineFromStringPieces("HTTP/1.1", "200", "OK");
+ headers.AppendHeader("key1", "value1");
+ interface_->SendSynReply(18, headers);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ DataFrame* df = connection_->output_list()->front();
+ ASSERT_EQ(expected, StringPiece(df->data, df->size));
+}
+
+TEST_F(FlipHttpSMTest, SendDataFrame) {
+ std::string data = "foo bar baz";
+ interface_->SendDataFrame(12, data.data(), data.size(), 0, false);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ DataFrame* df = connection_->output_list()->front();
+ ASSERT_EQ("b\r\nfoo bar baz\r\n", StringPiece(df->data, df->size));
+}
+
+TEST_F(FlipHttpSMProxyTest, ProcessBodyData) {
+ BalsaVisitorInterface* visitor = interface_.get();
+ std::string data = "hello, world";
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_,
+ SendDataFrame(0, data.data(), data.size(), 0, false));
+ }
+ visitor->ProcessBodyData(data.data(), data.size());
+}
+
+// --
+// FlipHttpSMProxyTest
+
+TEST_F(FlipHttpSMProxyTest, ProcessHeaders) {
+ BalsaVisitorInterface* visitor = interface_.get();
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendSynReply(0, _));
+ }
+ BalsaHeaders headers;
+ visitor->ProcessHeaders(headers);
+}
+
+TEST_F(FlipHttpSMProxyTest, MessageDone) {
+ BalsaVisitorInterface* visitor = interface_.get();
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendEOF(0));
+ }
+ visitor->MessageDone();
+}
+
+TEST_F(FlipHttpSMProxyTest, Cleanup) {
+ EXPECT_CALL(*connection_, Cleanup()).Times(0);
+ interface_->Cleanup();
+}
+
+TEST_F(FlipHttpSMProxyTest, SendEOF) {
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, ResetForNewInterface(_));
+ }
+ interface_->SendEOF(32);
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ DataFrame* df = connection_->output_list()->front();
+ ASSERT_EQ("0\r\n\r\n", StringPiece(df->data, df->size));
+}
+
+// --
+// FlipHttpSMHttpTest
+
+TEST_F(FlipHttpSMHttpTest, ProcessHeaders) {
+ BalsaVisitorInterface* visitor = interface_.get();
+ {
+ BalsaHeaders headers;
+ std::string filename = "GET_/path/file";
+ memory_cache_->InsertFile(&headers, filename, "");
+ }
+
+ BalsaHeaders headers;
+ headers.AppendHeader("Host", "example.com");
+ headers.SetRequestFirstlineFromStringPieces("GET", "/path/file", "HTTP/1.0");
+ uint32 stream_id = 133;
+ interface_->SetStreamID(stream_id);
+ ASSERT_FALSE(HasStream(stream_id));
+ visitor->ProcessHeaders(headers);
+ ASSERT_TRUE(HasStream(stream_id));
+}
+
+TEST_F(FlipHttpSMHttpTest, MessageDone) {
+ BalsaVisitorInterface* visitor = interface_.get();
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendEOF(0)).Times(0);
+ }
+ visitor->MessageDone();
+}
+
+TEST_F(FlipHttpSMHttpTest, Cleanup) {
+ EXPECT_CALL(*connection_, Cleanup()).Times(0);
+ interface_->Cleanup();
+}
+
+TEST_F(FlipHttpSMHttpTest, SendEOF) {
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, ResetForNewInterface(_)).Times(0);
+ }
+ interface_->SendEOF(32);
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ DataFrame* df = connection_->output_list()->front();
+ ASSERT_EQ("0\r\n\r\n", StringPiece(df->data, df->size));
+}
+
+// --
+// FlipHttpSMSpdyTest
+
+TEST_F(FlipHttpSMSpdyTest, ProcessHeaders) {
+ BalsaVisitorInterface* visitor = interface_.get();
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendSynReply(0, _));
+ }
+ BalsaHeaders headers;
+ visitor->ProcessHeaders(headers);
+}
+
+TEST_F(FlipHttpSMSpdyTest, MessageDone) {
+ BalsaVisitorInterface* visitor = interface_.get();
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, SendEOF(0)).Times(0);
+ }
+ visitor->MessageDone();
+}
+
+TEST_F(FlipHttpSMSpdyTest, Cleanup) {
+ EXPECT_CALL(*connection_, Cleanup()).Times(0);
+ interface_->Cleanup();
+}
+
+TEST_F(FlipHttpSMSpdyTest, SendEOF) {
+ {
+ InSequence s;
+ EXPECT_CALL(*mock_another_interface_, ResetForNewInterface(_)).Times(0);
+ }
+ interface_->SendEOF(32);
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ DataFrame* df = connection_->output_list()->front();
+ ASSERT_EQ("0\r\n\r\n", StringPiece(df->data, df->size));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/tools/flip_server/http_message_constants.cc b/net/tools/flip_server/http_message_constants.cc
new file mode 100644
index 0000000..a4a8b4d
--- /dev/null
+++ b/net/tools/flip_server/http_message_constants.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/http_message_constants.h"
+
+namespace net {
+
+const char* get_http_status_message(int status_message) {
+ switch (status_message) {
+ case 100:
+ return "Continue";
+ case 101:
+ return "Switching Protocols";
+ case 200:
+ return "OK";
+ case 201:
+ return "Created";
+ case 202:
+ return "Accepted";
+ case 203:
+ return "Non-Authoritative Information";
+ case 204:
+ return "No Content";
+ case 205:
+ return "Reset Content";
+ case 206:
+ return "Partial Content";
+ case 300:
+ return "Multiple Choices";
+ case 301:
+ return "Moved Permanently";
+ case 302:
+ return "Found";
+ case 303:
+ return "See Other";
+ case 304:
+ return "Not Modified";
+ case 305:
+ return "Use Proxy";
+ case 307:
+ return "Temporary Redirect";
+ case 400:
+ return "Bad Request";
+ case 401:
+ return "Unauthorized";
+ case 402:
+ return "Payment Required";
+ case 403:
+ return "Forbidden";
+ case 404:
+ return "Not Found";
+ case 405:
+ return "Method Not Allowed";
+ case 406:
+ return "Not Acceptable";
+ case 407:
+ return "Proxy Authentication Required";
+ case 408:
+ return "Request Time-out";
+ case 409:
+ return "Conflict";
+ case 410:
+ return "Gone";
+ case 411:
+ return "Length Required";
+ case 412:
+ return "Precondition Failed";
+ case 413:
+ return "Request Entity Too Large";
+ case 414:
+ return "Request-URI Too Large";
+ case 415:
+ return "Unsupported Media Type";
+ case 416:
+ return "Requested range not satisfiable";
+ case 417:
+ return "Expectation Failed";
+ case 500:
+ return "Internal Server Error";
+ case 501:
+ return "Not Implemented";
+ case 502:
+ return "Bad Gateway";
+ case 503:
+ return "Service Unavailable";
+ case 504:
+ return "Gateway Time-out";
+ case 505:
+ return "HTTP Version not supported";
+ }
+ return "unknown";
+}
+
+const int http_status_codes[] = {
+ 100, 101, 200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304,
+ 305, 307, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411,
+ 412, 413, 414, 415, 416, 417, 500, 501, 502, 503, 504, 505};
+
+const int http_status_code_count =
+ sizeof(http_status_codes) / sizeof(http_status_codes[0]);
+
+} // namespace net
diff --git a/net/tools/flip_server/loadtime_measurement.h b/net/tools/flip_server/loadtime_measurement.h
new file mode 100644
index 0000000..d18ac2f
--- /dev/null
+++ b/net/tools/flip_server/loadtime_measurement.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_LOADTIME_MEASUREMENT_H_
+#define NET_TOOLS_FLIP_SERVER_LOADTIME_MEASUREMENT_H_
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/strings/string_split.h"
+
+// Class to handle loadtime measure related urls, which all start with testing
+// The in memory server has a singleton object of this class. It includes a
+// html file containing javascript to go through a list of urls and upload the
+// loadtime. The users can modify urls.txt to define the urls they want to
+// measure and start with downloading the html file from browser.
+class LoadtimeMeasurement {
+ public:
+ LoadtimeMeasurement(const std::string& urls_file,
+ const std::string& pageload_html_file)
+ : num_urls_(0), pageload_html_file_(pageload_html_file) {
+ std::string urls_string;
+ base::ReadFileToString(urls_file, &urls_string);
+ base::SplitString(urls_string, '\n', &urls_);
+ num_urls_ = urls_.size();
+ }
+
+ // This is the entry function for all the loadtime measure related urls
+ // It handles the request to html file, get_total_iteration to get number
+ // of urls in the urls file, get each url, report the loadtime for
+ // each url, and the test is completed.
+ void ProcessRequest(const std::string& uri, std::string& output) {
+ // remove "/testing/" from uri to get the action
+ std::string action = uri.substr(9);
+ if (pageload_html_file_.find(action) != std::string::npos) {
+ base::ReadFileToString(pageload_html_file_, &output);
+ return;
+ }
+ if (action.find("get_total_iteration") == 0) {
+ char buffer[16];
+ snprintf(buffer, sizeof(buffer), "%d", num_urls_);
+ output.append(buffer, strlen(buffer));
+ return;
+ }
+ if (action.find("geturl") == 0) {
+ size_t b = action.find_first_of('=');
+ if (b != std::string::npos) {
+ int num = atoi(action.substr(b + 1).c_str());
+ if (num < num_urls_) {
+ output.append(urls_[num]);
+ }
+ }
+ return;
+ }
+ if (action.find("test_complete") == 0) {
+ for (std::map<std::string, int>::const_iterator it = loadtimes_.begin();
+ it != loadtimes_.end();
+ ++it) {
+ LOG(INFO) << it->first << " " << it->second;
+ }
+ loadtimes_.clear();
+ output.append("OK");
+ return;
+ }
+ if (action.find("record_page_load") == 0) {
+ std::vector<std::string> query;
+ base::SplitString(action, '?', &query);
+ std::vector<std::string> params;
+ base::SplitString(query[1], '&', ¶ms);
+ std::vector<std::string> url;
+ std::vector<std::string> loadtime;
+ base::SplitString(params[1], '=', &url);
+ base::SplitString(params[2], '=', &loadtime);
+ loadtimes_[url[1]] = atoi(loadtime[1].c_str());
+ output.append("OK");
+ return;
+ }
+ }
+
+ private:
+ int num_urls_;
+ std::vector<std::string> urls_;
+ std::map<std::string, int> loadtimes_;
+ const std::string pageload_html_file_;
+};
+
+#endif // NET_TOOLS_FLIP_SERVER_LOADTIME_MEASUREMENT_H_
diff --git a/net/tools/flip_server/mem_cache.cc b/net/tools/flip_server/mem_cache.cc
new file mode 100644
index 0000000..521f56c
--- /dev/null
+++ b/net/tools/flip_server/mem_cache.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/mem_cache.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <deque>
+#include <map>
+#include <string>
+
+#include "base/strings/string_util.h"
+#include "net/tools/balsa/balsa_frame.h"
+#include "net/tools/balsa/balsa_headers.h"
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+#include "net/tools/dump_cache/url_utilities.h"
+
+namespace {
+// The directory where cache locates);
+const char FLAGS_cache_base_dir[] = ".";
+} // namespace
+
+namespace net {
+
+void StoreBodyAndHeadersVisitor::ProcessBodyData(const char* input,
+ size_t size) {
+ body.append(input, size);
+}
+
+void StoreBodyAndHeadersVisitor::HandleHeaderError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StoreBodyAndHeadersVisitor::HandleHeaderWarning(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StoreBodyAndHeadersVisitor::HandleChunkingError(BalsaFrame* framer) {
+ HandleError();
+}
+
+void StoreBodyAndHeadersVisitor::HandleBodyError(BalsaFrame* framer) {
+ HandleError();
+}
+
+FileData::FileData(const BalsaHeaders* headers,
+ const std::string& filename,
+ const std::string& body)
+ : filename_(filename), body_(body) {
+ if (headers) {
+ headers_.reset(new BalsaHeaders);
+ headers_->CopyFrom(*headers);
+ }
+}
+
+FileData::FileData() {}
+
+FileData::~FileData() {}
+
+MemoryCache::MemoryCache() : cwd_(FLAGS_cache_base_dir) {}
+
+MemoryCache::~MemoryCache() { ClearFiles(); }
+
+void MemoryCache::CloneFrom(const MemoryCache& mc) {
+ DCHECK_NE(this, &mc);
+ ClearFiles();
+ files_ = mc.files_;
+ cwd_ = mc.cwd_;
+}
+
+void MemoryCache::AddFiles() {
+ std::deque<std::string> paths;
+ paths.push_back(cwd_ + "/GET_");
+ DIR* current_dir = NULL;
+ while (!paths.empty()) {
+ while (current_dir == NULL && !paths.empty()) {
+ std::string current_dir_name = paths.front();
+ VLOG(1) << "Attempting to open dir: \"" << current_dir_name << "\"";
+ current_dir = opendir(current_dir_name.c_str());
+ paths.pop_front();
+
+ if (current_dir == NULL) {
+ perror("Unable to open directory. ");
+ current_dir_name.clear();
+ continue;
+ }
+
+ if (current_dir) {
+ VLOG(1) << "Succeeded opening";
+ for (struct dirent* dir_data = readdir(current_dir); dir_data != NULL;
+ dir_data = readdir(current_dir)) {
+ std::string current_entry_name =
+ current_dir_name + "/" + dir_data->d_name;
+ if (dir_data->d_type == DT_REG) {
+ VLOG(1) << "Found file: " << current_entry_name;
+ ReadAndStoreFileContents(current_entry_name.c_str());
+ } else if (dir_data->d_type == DT_DIR) {
+ VLOG(1) << "Found subdir: " << current_entry_name;
+ if (std::string(dir_data->d_name) != "." &&
+ std::string(dir_data->d_name) != "..") {
+ VLOG(1) << "Adding to search path: " << current_entry_name;
+ paths.push_front(current_entry_name);
+ }
+ }
+ }
+ VLOG(1) << "Oops, no data left. Closing dir.";
+ closedir(current_dir);
+ current_dir = NULL;
+ }
+ }
+ }
+}
+
+void MemoryCache::ReadToString(const char* filename, std::string* output) {
+ output->clear();
+ int fd = open(filename, 0, "r");
+ if (fd == -1)
+ return;
+ char buffer[4096];
+ ssize_t read_status = read(fd, buffer, sizeof(buffer));
+ while (read_status > 0) {
+ output->append(buffer, static_cast<size_t>(read_status));
+ do {
+ read_status = read(fd, buffer, sizeof(buffer));
+ } while (read_status <= 0 && errno == EINTR);
+ }
+ close(fd);
+}
+
+void MemoryCache::ReadAndStoreFileContents(const char* filename) {
+ StoreBodyAndHeadersVisitor visitor;
+ BalsaFrame framer;
+ framer.set_balsa_visitor(&visitor);
+ framer.set_balsa_headers(&(visitor.headers));
+ std::string filename_contents;
+ ReadToString(filename, &filename_contents);
+
+ // Ugly hack to make everything look like 1.1.
+ if (filename_contents.find("HTTP/1.0") == 0)
+ filename_contents[7] = '1';
+
+ size_t pos = 0;
+ size_t old_pos = 0;
+ while (true) {
+ old_pos = pos;
+ pos += framer.ProcessInput(filename_contents.data() + pos,
+ filename_contents.size() - pos);
+ if (framer.Error() || pos == old_pos) {
+ LOG(ERROR) << "Unable to make forward progress, or error"
+ " framing file: " << filename;
+ if (framer.Error()) {
+ LOG(INFO) << "********************************************ERROR!";
+ return;
+ }
+ return;
+ }
+ if (framer.MessageFullyRead()) {
+ // If no Content-Length or Transfer-Encoding was captured in the
+ // file, then the rest of the data is the body. Many of the captures
+ // from within Chrome don't have content-lengths.
+ if (!visitor.body.length())
+ visitor.body = filename_contents.substr(pos);
+ break;
+ }
+ }
+ visitor.headers.RemoveAllOfHeader("content-length");
+ visitor.headers.RemoveAllOfHeader("transfer-encoding");
+ visitor.headers.RemoveAllOfHeader("connection");
+ visitor.headers.AppendHeader("transfer-encoding", "chunked");
+ visitor.headers.AppendHeader("connection", "keep-alive");
+
+// Experiment with changing headers for forcing use of cached
+// versions of content.
+// TODO(mbelshe) REMOVE ME
+#if 0
+ // TODO(mbelshe) append current date.
+ visitor.headers.RemoveAllOfHeader("date");
+ if (visitor.headers.HasHeader("expires")) {
+ visitor.headers.RemoveAllOfHeader("expires");
+ visitor.headers.AppendHeader("expires",
+ "Fri, 30 Aug, 2019 12:00:00 GMT");
+ }
+#endif
+ DCHECK_GE(std::string(filename).size(), cwd_.size() + 1);
+ DCHECK_EQ(std::string(filename).substr(0, cwd_.size()), cwd_);
+ DCHECK_EQ(filename[cwd_.size()], '/');
+ std::string filename_stripped = std::string(filename).substr(cwd_.size() + 1);
+ LOG(INFO) << "Adding file (" << visitor.body.length()
+ << " bytes): " << filename_stripped;
+ size_t slash_pos = filename_stripped.find('/');
+ if (slash_pos == std::string::npos) {
+ slash_pos = filename_stripped.size();
+ }
+ InsertFile(
+ &visitor.headers, filename_stripped.substr(0, slash_pos), visitor.body);
+}
+
+FileData* MemoryCache::GetFileData(const std::string& filename) {
+ Files::iterator fi = files_.end();
+ if (EndsWith(filename, ".html", true)) {
+ fi = files_.find(filename.substr(0, filename.size() - 5) + ".http");
+ }
+ if (fi == files_.end())
+ fi = files_.find(filename);
+
+ if (fi == files_.end()) {
+ return NULL;
+ }
+ return fi->second;
+}
+
+bool MemoryCache::AssignFileData(const std::string& filename,
+ MemCacheIter* mci) {
+ mci->file_data = GetFileData(filename);
+ if (mci->file_data == NULL) {
+ LOG(ERROR) << "Could not find file data for " << filename;
+ return false;
+ }
+ return true;
+}
+
+void MemoryCache::InsertFile(const BalsaHeaders* headers,
+ const std::string& filename,
+ const std::string& body) {
+ InsertFile(new FileData(headers, filename, body));
+}
+
+void MemoryCache::InsertFile(FileData* file_data) {
+ Files::iterator it = files_.find(file_data->filename());
+ if (it != files_.end()) {
+ delete it->second;
+ it->second = file_data;
+ } else {
+ files_.insert(std::make_pair(file_data->filename(), file_data));
+ }
+}
+
+void MemoryCache::ClearFiles() {
+ for (Files::const_iterator i = files_.begin(); i != files_.end(); ++i) {
+ delete i->second;
+ }
+ files_.clear();
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/mem_cache.h b/net/tools/flip_server/mem_cache.h
new file mode 100644
index 0000000..76ffc95
--- /dev/null
+++ b/net/tools/flip_server/mem_cache.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_MEM_CACHE_H_
+#define NET_TOOLS_FLIP_SERVER_MEM_CACHE_H_
+
+#include <map>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/tools/balsa/balsa_headers.h"
+#include "net/tools/balsa/balsa_visitor_interface.h"
+#include "net/tools/flip_server/constants.h"
+
+namespace net {
+
+class StoreBodyAndHeadersVisitor : public BalsaVisitorInterface {
+ public:
+ void HandleError() { error_ = true; }
+
+ // BalsaVisitorInterface:
+ virtual void ProcessBodyInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE;
+ virtual void ProcessHeaderInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaders(const BalsaHeaders& headers) OVERRIDE {
+ // nothing to do here-- we're assuming that the BalsaFrame has
+ // been handed our headers.
+ }
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(const char* line_input,
+ size_t line_length,
+ const char* version_input,
+ size_t version_length,
+ const char* status_input,
+ size_t status_length,
+ const char* reason_input,
+ size_t reason_length) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t chunk_length) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char* input, size_t size) OVERRIDE {
+ }
+ virtual void HeaderDone() OVERRIDE {}
+ virtual void MessageDone() OVERRIDE {}
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE;
+
+ BalsaHeaders headers;
+ std::string body;
+ bool error_;
+};
+
+class FileData {
+ public:
+ FileData();
+ FileData(const BalsaHeaders* headers,
+ const std::string& filename,
+ const std::string& body);
+ ~FileData();
+
+ BalsaHeaders* headers() { return headers_.get(); }
+ const BalsaHeaders* headers() const { return headers_.get(); }
+
+ const std::string& filename() { return filename_; }
+ const std::string& body() { return body_; }
+
+ private:
+ scoped_ptr<BalsaHeaders> headers_;
+ std::string filename_;
+ std::string body_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileData);
+};
+
+class MemCacheIter {
+ public:
+ MemCacheIter()
+ : file_data(NULL),
+ priority(0),
+ transformed_header(false),
+ body_bytes_consumed(0),
+ stream_id(0),
+ max_segment_size(kInitialDataSendersThreshold),
+ bytes_sent(0) {}
+ explicit MemCacheIter(FileData* fd)
+ : file_data(fd),
+ priority(0),
+ transformed_header(false),
+ body_bytes_consumed(0),
+ stream_id(0),
+ max_segment_size(kInitialDataSendersThreshold),
+ bytes_sent(0) {}
+ FileData* file_data;
+ int priority;
+ bool transformed_header;
+ size_t body_bytes_consumed;
+ uint32 stream_id;
+ uint32 max_segment_size;
+ size_t bytes_sent;
+};
+
+class MemoryCache {
+ public:
+ typedef std::map<std::string, FileData*> Files;
+
+ public:
+ MemoryCache();
+ virtual ~MemoryCache();
+
+ void CloneFrom(const MemoryCache& mc);
+
+ void AddFiles();
+
+ // virtual for unittests
+ virtual void ReadToString(const char* filename, std::string* output);
+
+ void ReadAndStoreFileContents(const char* filename);
+
+ FileData* GetFileData(const std::string& filename);
+
+ bool AssignFileData(const std::string& filename, MemCacheIter* mci);
+
+ // For unittests
+ void InsertFile(const BalsaHeaders* headers,
+ const std::string& filename,
+ const std::string& body);
+
+ private:
+ void InsertFile(FileData* file_data);
+ void ClearFiles();
+
+ Files files_;
+ std::string cwd_;
+};
+
+class NotifierInterface {
+ public:
+ virtual ~NotifierInterface() {}
+ virtual void Notify() = 0;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_MEM_CACHE_H_
diff --git a/net/tools/flip_server/mem_cache_test.cc b/net/tools/flip_server/mem_cache_test.cc
new file mode 100644
index 0000000..5e45bb5
--- /dev/null
+++ b/net/tools/flip_server/mem_cache_test.cc
@@ -0,0 +1,101 @@
+// Copyright 2013 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.
+
+#include "net/tools/flip_server/mem_cache.h"
+
+#include "net/tools/balsa/balsa_headers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MemoryCacheWithFakeReadToString : public MemoryCache {
+ public:
+ virtual ~MemoryCacheWithFakeReadToString() {}
+
+ virtual void ReadToString(const char* filename,
+ std::string* output) OVERRIDE {
+ *output = data_map_[filename];
+ }
+
+ std::map<std::string, std::string> data_map_;
+};
+
+class FlipMemoryCacheTest : public ::testing::Test {
+ public:
+ FlipMemoryCacheTest() : mem_cache_(new MemoryCacheWithFakeReadToString) {}
+
+ protected:
+ scoped_ptr<MemoryCacheWithFakeReadToString> mem_cache_;
+};
+
+TEST_F(FlipMemoryCacheTest, EmptyCache) {
+ MemCacheIter mci;
+ mci.stream_id = 0;
+ ASSERT_EQ(NULL, mem_cache_->GetFileData("./foo"));
+ ASSERT_EQ(NULL, mem_cache_->GetFileData("./bar"));
+ ASSERT_FALSE(mem_cache_->AssignFileData("./hello", &mci));
+}
+
+TEST_F(FlipMemoryCacheTest, ReadAndStoreFileContents) {
+ FileData* foo;
+ FileData* hello;
+
+ mem_cache_->data_map_["./foo"] = "bar";
+ mem_cache_->data_map_["./hello"] =
+ "HTTP/1.0 200 OK\r\n"
+ "key1: value1\r\n"
+ "key2: value2\r\n\r\n"
+ "body: body\r\n";
+ mem_cache_->ReadAndStoreFileContents("./foo");
+ mem_cache_->ReadAndStoreFileContents("./hello");
+
+ foo = mem_cache_->GetFileData("foo");
+ hello = mem_cache_->GetFileData("hello");
+
+ // "./foo" content is broken.
+ ASSERT_EQ(NULL, foo);
+ ASSERT_FALSE(NULL == hello);
+ ASSERT_EQ(hello, mem_cache_->GetFileData("hello"));
+
+ // "HTTP/1.0" is rewritten to "HTTP/1.1".
+ ASSERT_EQ("HTTP/1.1", hello->headers()->response_version());
+ ASSERT_EQ("200", hello->headers()->response_code());
+ ASSERT_EQ("OK", hello->headers()->response_reason_phrase());
+ ASSERT_EQ(4,
+ std::distance(hello->headers()->header_lines_begin(),
+ hello->headers()->header_lines_end()));
+ ASSERT_TRUE(hello->headers()->HasHeader("key1"));
+ ASSERT_TRUE(hello->headers()->HasHeader("key2"));
+ ASSERT_TRUE(hello->headers()->HasHeader("transfer-encoding"));
+ ASSERT_TRUE(hello->headers()->HasHeader("connection"));
+ ASSERT_EQ("value1", hello->headers()->GetHeaderPosition("key1")->second);
+ ASSERT_EQ("value2", hello->headers()->GetHeaderPosition("key2")->second);
+ ASSERT_EQ("chunked",
+ hello->headers()->GetHeaderPosition("transfer-encoding")->second);
+ ASSERT_EQ("keep-alive",
+ hello->headers()->GetHeaderPosition("connection")->second);
+ ASSERT_EQ("body: body\r\n", hello->body());
+ ASSERT_EQ("hello", hello->filename());
+}
+
+TEST_F(FlipMemoryCacheTest, GetFileDataForHtmlFile) {
+ FileData* hello_html;
+
+ mem_cache_->data_map_["./hello.http"] =
+ "HTTP/1.0 200 OK\r\n"
+ "key1: value1\r\n"
+ "key2: value2\r\n\r\n"
+ "body: body\r\n";
+
+ mem_cache_->ReadAndStoreFileContents("./hello.http");
+ hello_html = mem_cache_->GetFileData("hello.html");
+ ASSERT_FALSE(NULL == hello_html);
+ ASSERT_EQ(hello_html, mem_cache_->GetFileData("hello.http"));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/tools/flip_server/output_ordering.cc b/net/tools/flip_server/output_ordering.cc
new file mode 100644
index 0000000..9659954
--- /dev/null
+++ b/net/tools/flip_server/output_ordering.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/output_ordering.h"
+
+#include <utility>
+
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+
+namespace net {
+
+OutputOrdering::PriorityMapPointer::PriorityMapPointer()
+ : ring(NULL), alarm_enabled(false) {}
+
+OutputOrdering::PriorityMapPointer::~PriorityMapPointer() {}
+
+// static
+double OutputOrdering::server_think_time_in_s_ = 0.0;
+
+OutputOrdering::OutputOrdering(SMConnectionInterface* connection)
+ : first_data_senders_threshold_(kInitialDataSendersThreshold),
+ connection_(connection) {
+ if (connection)
+ epoll_server_ = connection->epoll_server();
+}
+
+OutputOrdering::~OutputOrdering() { Reset(); }
+
+void OutputOrdering::Reset() {
+ while (!stream_ids_.empty()) {
+ StreamIdToPriorityMap::iterator sitpmi = stream_ids_.begin();
+ PriorityMapPointer& pmp = sitpmi->second;
+ if (pmp.alarm_enabled) {
+ epoll_server_->UnregisterAlarm(pmp.alarm_token);
+ }
+ stream_ids_.erase(sitpmi);
+ }
+ priority_map_.clear();
+ first_data_senders_.clear();
+}
+
+bool OutputOrdering::ExistsInPriorityMaps(uint32 stream_id) const {
+ StreamIdToPriorityMap::const_iterator sitpmi = stream_ids_.find(stream_id);
+ return sitpmi != stream_ids_.end();
+}
+
+OutputOrdering::BeginOutputtingAlarm::BeginOutputtingAlarm(
+ OutputOrdering* oo,
+ OutputOrdering::PriorityMapPointer* pmp,
+ const MemCacheIter& mci)
+ : output_ordering_(oo), pmp_(pmp), mci_(mci), epoll_server_(NULL) {}
+
+OutputOrdering::BeginOutputtingAlarm::~BeginOutputtingAlarm() {
+ if (epoll_server_ && pmp_->alarm_enabled)
+ epoll_server_->UnregisterAlarm(pmp_->alarm_token);
+}
+
+int64 OutputOrdering::BeginOutputtingAlarm::OnAlarm() {
+ OnUnregistration();
+ output_ordering_->MoveToActive(pmp_, mci_);
+ VLOG(2) << "ON ALARM! Should now start to output...";
+ delete this;
+ return 0;
+}
+
+void OutputOrdering::BeginOutputtingAlarm::OnRegistration(
+ const EpollServer::AlarmRegToken& tok,
+ EpollServer* eps) {
+ epoll_server_ = eps;
+ pmp_->alarm_token = tok;
+ pmp_->alarm_enabled = true;
+}
+
+void OutputOrdering::BeginOutputtingAlarm::OnUnregistration() {
+ pmp_->alarm_enabled = false;
+ delete this;
+}
+
+void OutputOrdering::BeginOutputtingAlarm::OnShutdown(EpollServer* eps) {
+ OnUnregistration();
+}
+
+void OutputOrdering::MoveToActive(PriorityMapPointer* pmp, MemCacheIter mci) {
+ VLOG(2) << "Moving to active!";
+ first_data_senders_.push_back(mci);
+ pmp->ring = &first_data_senders_;
+ pmp->it = first_data_senders_.end();
+ --pmp->it;
+ connection_->ReadyToSend();
+}
+
+void OutputOrdering::AddToOutputOrder(const MemCacheIter& mci) {
+ if (ExistsInPriorityMaps(mci.stream_id))
+ LOG(ERROR) << "OOps, already was inserted here?!";
+
+ double think_time_in_s = server_think_time_in_s_;
+ std::string x_server_latency =
+ mci.file_data->headers()->GetHeader("X-Server-Latency").as_string();
+ if (!x_server_latency.empty()) {
+ char* endp;
+ double tmp_think_time_in_s = strtod(x_server_latency.c_str(), &endp);
+ if (endp != x_server_latency.c_str() + x_server_latency.size()) {
+ LOG(ERROR) << "Unable to understand X-Server-Latency of: "
+ << x_server_latency
+ << " for resource: " << mci.file_data->filename().c_str();
+ } else {
+ think_time_in_s = tmp_think_time_in_s;
+ }
+ }
+ StreamIdToPriorityMap::iterator sitpmi;
+ sitpmi = stream_ids_.insert(std::pair<uint32, PriorityMapPointer>(
+ mci.stream_id, PriorityMapPointer())).first;
+ PriorityMapPointer& pmp = sitpmi->second;
+
+ BeginOutputtingAlarm* boa = new BeginOutputtingAlarm(this, &pmp, mci);
+ VLOG(1) << "Server think time: " << think_time_in_s;
+ epoll_server_->RegisterAlarmApproximateDelta(think_time_in_s * 1000000, boa);
+}
+
+void OutputOrdering::SpliceToPriorityRing(PriorityRing::iterator pri) {
+ MemCacheIter& mci = *pri;
+ PriorityMap::iterator pmi = priority_map_.find(mci.priority);
+ if (pmi == priority_map_.end()) {
+ pmi = priority_map_.insert(std::pair<uint32, PriorityRing>(
+ mci.priority, PriorityRing())).first;
+ }
+
+ pmi->second.splice(pmi->second.end(), first_data_senders_, pri);
+ StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(mci.stream_id);
+ sitpmi->second.ring = &(pmi->second);
+}
+
+MemCacheIter* OutputOrdering::GetIter() {
+ while (!first_data_senders_.empty()) {
+ MemCacheIter& mci = first_data_senders_.front();
+ if (mci.bytes_sent >= first_data_senders_threshold_) {
+ SpliceToPriorityRing(first_data_senders_.begin());
+ } else {
+ first_data_senders_.splice(first_data_senders_.end(),
+ first_data_senders_,
+ first_data_senders_.begin());
+ mci.max_segment_size = kInitialDataSendersThreshold;
+ return &mci;
+ }
+ }
+ while (!priority_map_.empty()) {
+ PriorityRing& first_ring = priority_map_.begin()->second;
+ if (first_ring.empty()) {
+ priority_map_.erase(priority_map_.begin());
+ continue;
+ }
+ MemCacheIter& mci = first_ring.front();
+ first_ring.splice(first_ring.end(), first_ring, first_ring.begin());
+ mci.max_segment_size = kSpdySegmentSize;
+ return &mci;
+ }
+ return NULL;
+}
+
+void OutputOrdering::RemoveStreamId(uint32 stream_id) {
+ StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(stream_id);
+ if (sitpmi == stream_ids_.end())
+ return;
+
+ PriorityMapPointer& pmp = sitpmi->second;
+ if (pmp.alarm_enabled)
+ epoll_server_->UnregisterAlarm(pmp.alarm_token);
+ else
+ pmp.ring->erase(pmp.it);
+ stream_ids_.erase(sitpmi);
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/output_ordering.h b/net/tools/flip_server/output_ordering.h
new file mode 100644
index 0000000..596f382
--- /dev/null
+++ b/net/tools/flip_server/output_ordering.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_OUTPUT_ORDERING_H_
+#define NET_TOOLS_FLIP_SERVER_OUTPUT_ORDERING_H_
+
+#include <list>
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/tools/epoll_server/epoll_server.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/mem_cache.h"
+
+namespace net {
+
+class SMConnectionInterface;
+
+class OutputOrdering {
+ public:
+ typedef std::list<MemCacheIter> PriorityRing;
+ typedef std::map<uint32, PriorityRing> PriorityMap;
+
+ struct PriorityMapPointer {
+ PriorityMapPointer();
+ ~PriorityMapPointer();
+ PriorityRing* ring;
+ PriorityRing::iterator it;
+ bool alarm_enabled;
+ EpollServer::AlarmRegToken alarm_token;
+ };
+
+ typedef std::map<uint32, PriorityMapPointer> StreamIdToPriorityMap;
+
+ StreamIdToPriorityMap stream_ids_;
+ PriorityMap priority_map_;
+ PriorityRing first_data_senders_;
+ uint32 first_data_senders_threshold_; // when you've passed this, you're no
+ // longer a first_data_sender...
+ SMConnectionInterface* connection_;
+ EpollServer* epoll_server_;
+
+ explicit OutputOrdering(SMConnectionInterface* connection);
+ ~OutputOrdering();
+ void Reset();
+ bool ExistsInPriorityMaps(uint32 stream_id) const;
+
+ struct BeginOutputtingAlarm : public EpollAlarmCallbackInterface {
+ public:
+ BeginOutputtingAlarm(OutputOrdering* oo,
+ OutputOrdering::PriorityMapPointer* pmp,
+ const MemCacheIter& mci);
+ virtual ~BeginOutputtingAlarm();
+
+ // EpollAlarmCallbackInterface:
+ virtual int64 OnAlarm() OVERRIDE;
+ virtual void OnRegistration(const EpollServer::AlarmRegToken& tok,
+ EpollServer* eps) OVERRIDE;
+ virtual void OnUnregistration() OVERRIDE;
+ virtual void OnShutdown(EpollServer* eps) OVERRIDE;
+
+ private:
+ OutputOrdering* output_ordering_;
+ OutputOrdering::PriorityMapPointer* pmp_;
+ MemCacheIter mci_;
+ EpollServer* epoll_server_;
+ };
+
+ void MoveToActive(PriorityMapPointer* pmp, MemCacheIter mci);
+ void AddToOutputOrder(const MemCacheIter& mci);
+ void SpliceToPriorityRing(PriorityRing::iterator pri);
+ MemCacheIter* GetIter();
+ void RemoveStreamId(uint32 stream_id);
+
+ static double server_think_time_in_s() { return server_think_time_in_s_; }
+ static void set_server_think_time_in_s(double value) {
+ server_think_time_in_s_ = value;
+ }
+
+ private:
+ static double server_think_time_in_s_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_OUTPUT_ORDERING_H_
diff --git a/net/tools/flip_server/ring_buffer.cc b/net/tools/flip_server/ring_buffer.cc
new file mode 100644
index 0000000..16c278b
--- /dev/null
+++ b/net/tools/flip_server/ring_buffer.cc
@@ -0,0 +1,241 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/ring_buffer.h"
+#include "base/logging.h"
+
+namespace net {
+
+RingBuffer::RingBuffer(int buffer_size)
+ : buffer_(new char[buffer_size]),
+ buffer_size_(buffer_size),
+ bytes_used_(0),
+ read_idx_(0),
+ write_idx_(0) {}
+
+RingBuffer::~RingBuffer() {}
+
+int RingBuffer::ReadableBytes() const { return bytes_used_; }
+
+int RingBuffer::BufferSize() const { return buffer_size_; }
+
+int RingBuffer::BytesFree() const { return BufferSize() - ReadableBytes(); }
+
+bool RingBuffer::Empty() const { return ReadableBytes() == 0; }
+
+bool RingBuffer::Full() const { return ReadableBytes() == BufferSize(); }
+
+// Returns the number of characters written.
+// Appends up-to-'size' bytes to the ringbuffer.
+int RingBuffer::Write(const char* bytes, int size) {
+ CHECK_GE(size, 0);
+#if 1
+ char* wptr;
+ int wsize;
+ GetWritablePtr(&wptr, &wsize);
+ int bytes_remaining = size;
+ int bytes_written = 0;
+
+ while (wsize && bytes_remaining) {
+ if (wsize > bytes_remaining) {
+ wsize = bytes_remaining;
+ }
+ memcpy(wptr, bytes + bytes_written, wsize);
+ bytes_written += wsize;
+ bytes_remaining -= wsize;
+ AdvanceWritablePtr(wsize);
+ GetWritablePtr(&wptr, &wsize);
+ }
+ return bytes_written;
+#else
+ const char* p = bytes;
+
+ int bytes_to_write = size;
+ int bytes_available = BytesFree();
+ if (bytes_available < bytes_to_write) {
+ bytes_to_write = bytes_available;
+ }
+ const char* end = bytes + bytes_to_write;
+
+ while (p != end) {
+ this->buffer_[this->write_idx_] = *p;
+ ++p;
+ ++this->write_idx_;
+ if (this->write_idx_ >= this->buffer_size_) {
+ this->write_idx_ = 0;
+ }
+ }
+ bytes_used_ += bytes_to_write;
+ return bytes_to_write;
+#endif
+}
+
+// Sets *ptr to the beginning of writable memory, and sets *size to the size
+// available for writing using this pointer.
+void RingBuffer::GetWritablePtr(char** ptr, int* size) const {
+ *ptr = buffer_.get() + write_idx_;
+
+ if (bytes_used_ == buffer_size_) {
+ *size = 0;
+ } else if (read_idx_ > write_idx_) {
+ *size = read_idx_ - write_idx_;
+ } else {
+ *size = buffer_size_ - write_idx_;
+ }
+}
+
+// Sets *ptr to the beginning of readable memory, and sets *size to the size
+// available for reading using this pointer.
+void RingBuffer::GetReadablePtr(char** ptr, int* size) const {
+ *ptr = buffer_.get() + read_idx_;
+
+ if (bytes_used_ == 0) {
+ *size = 0;
+ } else if (write_idx_ > read_idx_) {
+ *size = write_idx_ - read_idx_;
+ } else {
+ *size = buffer_size_ - read_idx_;
+ }
+}
+
+// returns the number of bytes read into
+int RingBuffer::Read(char* bytes, int size) {
+ CHECK_GE(size, 0);
+#if 1
+ char* rptr;
+ int rsize;
+ GetReadablePtr(&rptr, &rsize);
+ int bytes_remaining = size;
+ int bytes_read = 0;
+
+ while (rsize && bytes_remaining) {
+ if (rsize > bytes_remaining) {
+ rsize = bytes_remaining;
+ }
+ memcpy(bytes + bytes_read, rptr, rsize);
+ bytes_read += rsize;
+ bytes_remaining -= rsize;
+ AdvanceReadablePtr(rsize);
+ GetReadablePtr(&rptr, &rsize);
+ }
+ return bytes_read;
+#else
+ char* p = bytes;
+ int bytes_to_read = size;
+ int bytes_used = ReadableBytes();
+ if (bytes_used < bytes_to_read) {
+ bytes_to_read = bytes_used;
+ }
+ char* end = bytes + bytes_to_read;
+
+ while (p != end) {
+ *p = this->buffer_[this->read_idx_];
+ ++p;
+ ++this->read_idx_;
+ if (this->read_idx_ >= this->buffer_size_) {
+ this->read_idx_ = 0;
+ }
+ }
+ this->bytes_used_ -= bytes_to_read;
+ return bytes_to_read;
+#endif
+}
+
+void RingBuffer::Clear() {
+ bytes_used_ = 0;
+ write_idx_ = 0;
+ read_idx_ = 0;
+}
+
+bool RingBuffer::Reserve(int size) {
+ DCHECK_GT(size, 0);
+ char* write_ptr = NULL;
+ int write_size = 0;
+ GetWritablePtr(&write_ptr, &write_size);
+
+ if (write_size < size) {
+ char* read_ptr = NULL;
+ int read_size = 0;
+ GetReadablePtr(&read_ptr, &read_size);
+ if (size <= BytesFree()) {
+ // The fact that the total Free size is big enough but writable size is
+ // not means that the writeable region is broken into two pieces: only
+ // possible if the read_idx < write_idx. If write_idx < read_idx, then
+ // the writeable region must be contiguous: [write_idx, read_idx). There
+ // is no work to be done for the latter.
+ DCHECK_LE(read_idx_, write_idx_);
+ DCHECK_EQ(read_size, ReadableBytes());
+ if (read_idx_ < write_idx_) {
+ // Writeable area fragmented, consolidate it.
+ memmove(buffer_.get(), read_ptr, read_size);
+ read_idx_ = 0;
+ write_idx_ = read_size;
+ } else if (read_idx_ == write_idx_) {
+ // No unconsumed data in the buffer, simply reset the indexes.
+ DCHECK_EQ(ReadableBytes(), 0);
+ read_idx_ = 0;
+ write_idx_ = 0;
+ }
+ } else {
+ Resize(ReadableBytes() + size);
+ }
+ }
+ DCHECK_LE(size, buffer_size_ - write_idx_);
+ return true;
+}
+
+void RingBuffer::AdvanceReadablePtr(int amount_to_consume) {
+ CHECK_GE(amount_to_consume, 0);
+ if (amount_to_consume >= bytes_used_) {
+ Clear();
+ return;
+ }
+ read_idx_ += amount_to_consume;
+ read_idx_ %= buffer_size_;
+ bytes_used_ -= amount_to_consume;
+}
+
+void RingBuffer::AdvanceWritablePtr(int amount_to_produce) {
+ CHECK_GE(amount_to_produce, 0);
+ CHECK_LE(amount_to_produce, BytesFree());
+ write_idx_ += amount_to_produce;
+ write_idx_ %= buffer_size_;
+ bytes_used_ += amount_to_produce;
+}
+
+void RingBuffer::Resize(int buffer_size) {
+ CHECK_GE(buffer_size, 0);
+ if (buffer_size == buffer_size_)
+ return;
+
+ char* new_buffer = new char[buffer_size];
+ if (buffer_size < bytes_used_) {
+ // consume the oldest data.
+ AdvanceReadablePtr(bytes_used_ - buffer_size);
+ }
+
+ int bytes_written = 0;
+ int bytes_used = bytes_used_;
+ while (true) {
+ int size;
+ char* ptr;
+ GetReadablePtr(&ptr, &size);
+ if (size == 0)
+ break;
+ if (size > buffer_size) {
+ size = buffer_size;
+ }
+ memcpy(new_buffer + bytes_written, ptr, size);
+ bytes_written += size;
+ AdvanceReadablePtr(size);
+ }
+ buffer_.reset(new_buffer);
+
+ buffer_size_ = buffer_size;
+ bytes_used_ = bytes_used;
+ read_idx_ = 0;
+ write_idx_ = bytes_used_ % buffer_size_;
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/ring_buffer.h b/net/tools/flip_server/ring_buffer.h
new file mode 100644
index 0000000..b129ee8
--- /dev/null
+++ b/net/tools/flip_server/ring_buffer.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_RING_BUFFER_H__
+#define NET_TOOLS_FLIP_SERVER_RING_BUFFER_H__
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/tools/balsa/buffer_interface.h"
+
+namespace net {
+
+// The ring buffer is a circular buffer, that is, reads or writes may wrap
+// around the end of the linear memory contained by the class (and back to
+// the beginning). This is a good choice when you want to use a fixed amount
+// of buffering and don't want to be moving memory around a lot.
+//
+// What is the penalty for using this over a normal, linear buffer?
+// Reading all the data may take two operations, and
+// writing all the data may take two operations.
+//
+// In the proxy, this class is used as a fixed size buffer between
+// clients and servers (so that the memory size is constrained).
+
+class RingBuffer : public BufferInterface {
+ public:
+ explicit RingBuffer(int buffer_size);
+ virtual ~RingBuffer();
+
+ // Resize the buffer to the size specified here. If the buffer_size passed
+ // in here is smaller than the amount of data in the buffer, then the oldest
+ // data will be dropped, but all other data will be saved.
+ // This means: If the buffer size is increasing, all data that was resident
+ // in the buffer prior to this call will be resident after this call.
+ void Resize(int buffer_size);
+
+ // The following functions all override pure virtual functions
+ // in BufferInterface. See buffer_interface.h for a description
+ // of what they do if the function isn't documented here.
+ virtual int ReadableBytes() const OVERRIDE;
+ virtual int BufferSize() const OVERRIDE;
+ virtual int BytesFree() const OVERRIDE;
+
+ virtual bool Empty() const OVERRIDE;
+ virtual bool Full() const OVERRIDE;
+
+ // returns the number of characters written.
+ // appends up-to-'size' bytes to the ringbuffer.
+ virtual int Write(const char* bytes, int size) OVERRIDE;
+
+ // Stores a pointer into the ring buffer in *ptr, and stores the number of
+ // characters which are allowed to be written in *size.
+ // If there are no writable bytes available, then *size will contain 0.
+ virtual void GetWritablePtr(char** ptr, int* size) const OVERRIDE;
+
+ // Stores a pointer into the ring buffer in *ptr, and stores the number of
+ // characters which are allowed to be read in *size.
+ // If there are no readable bytes available, then *size will contain 0.
+ virtual void GetReadablePtr(char** ptr, int* size) const OVERRIDE;
+
+ // Returns the number of bytes read into 'bytes'.
+ virtual int Read(char* bytes, int size) OVERRIDE;
+
+ // Removes all data from the ring buffer.
+ virtual void Clear() OVERRIDE;
+
+ // Reserves contiguous writable empty space in the buffer of size bytes.
+ // Since the point of this class is to have a fixed size buffer, be careful
+ // not to inadvertently resize the buffer using Reserve(). If the reserve
+ // size is <= BytesFree(), it is guaranteed that the buffer size will not
+ // change.
+ // This can be an expensive operation, it may new a buffer copy all existing
+ // data and delete the old data. Even if the existing buffer does not need
+ // to be resized, unread data may still need to be non-destructively copied
+ // to consolidate fragmented free space. If the size requested is less than
+ // or equal to BytesFree(), it is guaranteed that the buffer size will not
+ // change.
+ virtual bool Reserve(int size) OVERRIDE;
+
+ // Removes the oldest 'amount_to_advance' characters.
+ // If amount_to_consume > ReadableBytes(), this performs a Clear() instead.
+ virtual void AdvanceReadablePtr(int amount_to_advance) OVERRIDE;
+
+ // Moves the internal pointers around such that the amount of data specified
+ // here is expected to already be resident (as if it was Written).
+ virtual void AdvanceWritablePtr(int amount_to_advance) OVERRIDE;
+
+ protected:
+ int read_idx() const { return read_idx_; }
+ int write_idx() const { return write_idx_; }
+ int bytes_used() const { return bytes_used_; }
+ int buffer_size() const { return buffer_size_; }
+ const char* buffer() const { return buffer_.get(); }
+
+ int set_read_idx(int idx) { return read_idx_ = idx; }
+ int set_write_idx(int idx) { return write_idx_ = idx; }
+
+ private:
+ scoped_ptr<char[]> buffer_;
+ int buffer_size_;
+ int bytes_used_;
+ int read_idx_;
+ int write_idx_;
+
+ RingBuffer(const RingBuffer&);
+ void operator=(const RingBuffer&);
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_RING_BUFFER_H__
diff --git a/net/tools/flip_server/run_all_tests.cc b/net/tools/flip_server/run_all_tests.cc
new file mode 100644
index 0000000..6acc286
--- /dev/null
+++ b/net/tools/flip_server/run_all_tests.cc
@@ -0,0 +1,8 @@
+// Copyright 2013 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.
+
+#include "base/test/test_suite.h"
+#include "build/build_config.h"
+
+int main(int argc, char** argv) { return base::TestSuite(argc, argv).Run(); }
diff --git a/net/tools/flip_server/sm_connection.cc b/net/tools/flip_server/sm_connection.cc
new file mode 100644
index 0000000..4acdea4
--- /dev/null
+++ b/net/tools/flip_server/sm_connection.cc
@@ -0,0 +1,666 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/sm_connection.h"
+
+#include <errno.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <list>
+#include <string>
+
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/http_interface.h"
+#include "net/tools/flip_server/spdy_interface.h"
+#include "net/tools/flip_server/spdy_ssl.h"
+#include "net/tools/flip_server/streamer_interface.h"
+
+namespace net {
+
+// static
+bool SMConnection::force_spdy_ = false;
+
+DataFrame::~DataFrame() {
+ if (delete_when_done)
+ delete[] data;
+}
+
+SMConnection::SMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix)
+ : last_read_time_(0),
+ fd_(-1),
+ events_(0),
+ registered_in_epoll_server_(false),
+ initialized_(false),
+ protocol_detected_(false),
+ connection_complete_(false),
+ connection_pool_(NULL),
+ epoll_server_(epoll_server),
+ ssl_state_(ssl_state),
+ memory_cache_(memory_cache),
+ acceptor_(acceptor),
+ read_buffer_(kSpdySegmentSize * 40),
+ sm_spdy_interface_(NULL),
+ sm_http_interface_(NULL),
+ sm_streamer_interface_(NULL),
+ sm_interface_(NULL),
+ log_prefix_(log_prefix),
+ max_bytes_sent_per_dowrite_(4096),
+ ssl_(NULL) {}
+
+SMConnection::~SMConnection() {
+ if (initialized())
+ Reset();
+}
+
+EpollServer* SMConnection::epoll_server() { return epoll_server_; }
+
+void SMConnection::ReadyToSend() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Setting ready to send: EPOLLIN | EPOLLOUT";
+ epoll_server_->SetFDReady(fd_, EPOLLIN | EPOLLOUT);
+}
+
+void SMConnection::EnqueueDataFrame(DataFrame* df) {
+ output_list_.push_back(df);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "EnqueueDataFrame: "
+ << "size = " << df->size << ": Setting FD ready.";
+ ReadyToSend();
+}
+
+void SMConnection::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ if (initialized_) {
+ LOG(FATAL) << "Attempted to initialize already initialized server";
+ return;
+ }
+
+ client_ip_ = remote_ip;
+
+ if (fd == -1) {
+ // If fd == -1, then we are initializing a new connection that will
+ // connect to the backend.
+ //
+ // ret: -1 == error
+ // 0 == connection in progress
+ // 1 == connection complete
+ // TODO(kelindsay): is_numeric_host_address value needs to be detected
+ server_ip_ = server_ip;
+ server_port_ = server_port;
+ int ret = CreateConnectedSocket(
+ &fd_, server_ip, server_port, true, acceptor_->disable_nagle_);
+
+ if (ret < 0) {
+ LOG(ERROR) << "-1 Could not create connected socket";
+ return;
+ } else if (ret == 1) {
+ DCHECK_NE(-1, fd_);
+ connection_complete_ = true;
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Connection complete to: " << server_ip_ << ":" << server_port_
+ << " ";
+ }
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Connecting to server: " << server_ip_ << ":" << server_port_
+ << " ";
+ } else {
+ // If fd != -1 then we are initializing a connection that has just been
+ // accepted from the listen socket.
+ connection_complete_ = true;
+ if (epoll_server_ && registered_in_epoll_server_ && fd_ != -1) {
+ epoll_server_->UnregisterFD(fd_);
+ }
+ if (fd_ != -1) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Closing pre-existing fd";
+ close(fd_);
+ fd_ = -1;
+ }
+
+ fd_ = fd;
+ }
+
+ registered_in_epoll_server_ = false;
+ // Set the last read time here as the idle checker will start from
+ // now.
+ last_read_time_ = time(NULL);
+ initialized_ = true;
+
+ connection_pool_ = connection_pool;
+ epoll_server_ = epoll_server;
+
+ if (sm_interface) {
+ sm_interface_ = sm_interface;
+ protocol_detected_ = true;
+ }
+
+ read_buffer_.Clear();
+
+ epoll_server_->RegisterFD(fd_, this, EPOLLIN | EPOLLOUT | EPOLLET);
+
+ if (use_ssl) {
+ ssl_ = CreateSSLContext(ssl_state_->ssl_ctx);
+ SSL_set_fd(ssl_, fd_);
+ PrintSslError();
+ }
+}
+
+void SMConnection::CorkSocket() {
+ int state = 1;
+ int rv = setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &state, sizeof(state));
+ if (rv < 0)
+ VLOG(1) << "setsockopt(CORK): " << errno;
+}
+
+void SMConnection::UncorkSocket() {
+ int state = 0;
+ int rv = setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &state, sizeof(state));
+ if (rv < 0)
+ VLOG(1) << "setsockopt(CORK): " << errno;
+}
+
+int SMConnection::Send(const char* data, int len, int flags) {
+ int rv = 0;
+ CorkSocket();
+ if (ssl_) {
+ ssize_t bytes_written = 0;
+ // Write smallish chunks to SSL so that we don't have large
+ // multi-packet TLS records to receive before being able to handle
+ // the data. We don't have to be too careful here, because our data
+ // frames are already getting chunked appropriately, and those are
+ // the most likely "big" frames.
+ while (len > 0) {
+ const int kMaxTLSRecordSize = 1500;
+ const char* ptr = &(data[bytes_written]);
+ int chunksize = std::min(len, kMaxTLSRecordSize);
+ rv = SSL_write(ssl_, ptr, chunksize);
+ VLOG(2) << "SSLWrite(" << chunksize << " bytes): " << rv;
+ if (rv <= 0) {
+ switch (SSL_get_error(ssl_, rv)) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_ACCEPT:
+ case SSL_ERROR_WANT_CONNECT:
+ rv = -2;
+ break;
+ default:
+ PrintSslError();
+ break;
+ }
+ break;
+ }
+ bytes_written += rv;
+ len -= rv;
+ if (rv != chunksize)
+ break; // If we couldn't write everything, we're implicitly stalled
+ }
+ // If we wrote some data, return that count. Otherwise
+ // return the stall error.
+ if (bytes_written > 0)
+ rv = bytes_written;
+ } else {
+ rv = send(fd_, data, len, flags);
+ }
+ if (!(flags & MSG_MORE))
+ UncorkSocket();
+ return rv;
+}
+
+void SMConnection::OnRegistration(EpollServer* eps, int fd, int event_mask) {
+ registered_in_epoll_server_ = true;
+}
+
+void SMConnection::OnEvent(int fd, EpollEvent* event) {
+ events_ |= event->in_events;
+ HandleEvents();
+ if (events_) {
+ event->out_ready_mask = events_;
+ events_ = 0;
+ }
+}
+
+void SMConnection::OnUnregistration(int fd, bool replaced) {
+ registered_in_epoll_server_ = false;
+}
+
+void SMConnection::OnShutdown(EpollServer* eps, int fd) {
+ Cleanup("OnShutdown");
+ return;
+}
+
+void SMConnection::Cleanup(const char* cleanup) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Cleanup: " << cleanup;
+ if (!initialized_)
+ return;
+ Reset();
+ if (connection_pool_)
+ connection_pool_->SMConnectionDone(this);
+ if (sm_interface_)
+ sm_interface_->ResetForNewConnection();
+ last_read_time_ = 0;
+}
+
+void SMConnection::HandleEvents() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Received: " << EpollServer::EventMaskToString(events_).c_str();
+
+ if (events_ & EPOLLIN) {
+ if (!DoRead())
+ goto handle_close_or_error;
+ }
+
+ if (events_ & EPOLLOUT) {
+ // Check if we have connected or not
+ if (connection_complete_ == false) {
+ int sock_error;
+ socklen_t sock_error_len = sizeof(sock_error);
+ int ret =
+ getsockopt(fd_, SOL_SOCKET, SO_ERROR, &sock_error, &sock_error_len);
+ if (ret != 0) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "getsockopt error: " << errno << ": " << strerror(errno);
+ goto handle_close_or_error;
+ }
+ if (sock_error == 0) {
+ connection_complete_ = true;
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Connection complete to " << server_ip_ << ":"
+ << server_port_ << " ";
+ } else if (sock_error == EINPROGRESS) {
+ return;
+ } else {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "error connecting to server";
+ goto handle_close_or_error;
+ }
+ }
+ if (!DoWrite())
+ goto handle_close_or_error;
+ }
+
+ if (events_ & (EPOLLHUP | EPOLLERR)) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "!!! Got HUP or ERR";
+ goto handle_close_or_error;
+ }
+ return;
+
+ handle_close_or_error:
+ Cleanup("HandleEvents");
+}
+
+// Decide if SPDY was negotiated.
+bool SMConnection::WasSpdyNegotiated(SpdyMajorVersion* version_negotiated) {
+ *version_negotiated = SPDY3;
+ if (force_spdy())
+ return true;
+
+ // If this is an SSL connection, check if NPN specifies SPDY.
+ if (ssl_) {
+ const unsigned char* npn_proto;
+ unsigned int npn_proto_len;
+ SSL_get0_next_proto_negotiated(ssl_, &npn_proto, &npn_proto_len);
+ if (npn_proto_len > 0) {
+ std::string npn_proto_str((const char*)npn_proto, npn_proto_len);
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "NPN protocol detected: " << npn_proto_str;
+ if (!strncmp(reinterpret_cast<const char*>(npn_proto),
+ "spdy/2",
+ npn_proto_len)) {
+ *version_negotiated = SPDY2;
+ return true;
+ }
+ if (!strncmp(reinterpret_cast<const char*>(npn_proto),
+ "spdy/3",
+ npn_proto_len)) {
+ *version_negotiated = SPDY3;
+ return true;
+ }
+ if (!strncmp(reinterpret_cast<const char*>(npn_proto),
+ "spdy/4a2",
+ npn_proto_len)) {
+ *version_negotiated = SPDY4;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool SMConnection::SetupProtocolInterfaces() {
+ DCHECK(!protocol_detected_);
+ protocol_detected_ = true;
+
+ SpdyMajorVersion version;
+ bool spdy_negotiated = WasSpdyNegotiated(&version);
+ bool using_ssl = ssl_ != NULL;
+
+ if (using_ssl)
+ VLOG(1) << (SSL_session_reused(ssl_) ? "Resumed" : "Renegotiated")
+ << " SSL Session.";
+
+ if (acceptor_->spdy_only_ && !spdy_negotiated) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "SPDY proxy only, closing HTTPS connection.";
+ return false;
+ }
+
+ switch (acceptor_->flip_handler_type_) {
+ case FLIP_HANDLER_HTTP_SERVER: {
+ DCHECK(!spdy_negotiated);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << (sm_http_interface_ ? "Creating" : "Reusing")
+ << " HTTP interface.";
+ if (!sm_http_interface_)
+ sm_http_interface_ = new HttpSM(this, NULL, memory_cache_, acceptor_);
+ sm_interface_ = sm_http_interface_;
+ break;
+ }
+ case FLIP_HANDLER_PROXY: {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << (sm_streamer_interface_ ? "Creating" : "Reusing")
+ << " PROXY Streamer interface.";
+ if (!sm_streamer_interface_) {
+ sm_streamer_interface_ =
+ new StreamerSM(this, NULL, epoll_server_, acceptor_);
+ sm_streamer_interface_->set_is_request();
+ }
+ sm_interface_ = sm_streamer_interface_;
+ // If spdy is not negotiated, the streamer interface will proxy all
+ // data to the origin server.
+ if (!spdy_negotiated)
+ break;
+ }
+ // Otherwise fall through into the case below.
+ case FLIP_HANDLER_SPDY_SERVER: {
+ DCHECK(spdy_negotiated);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << (sm_spdy_interface_ ? "Creating" : "Reusing")
+ << " SPDY interface.";
+ if (sm_spdy_interface_)
+ sm_spdy_interface_->CreateFramer(version);
+ else
+ sm_spdy_interface_ = new SpdySM(
+ this, NULL, epoll_server_, memory_cache_, acceptor_, version);
+ sm_interface_ = sm_spdy_interface_;
+ break;
+ }
+ }
+
+ CorkSocket();
+ if (!sm_interface_->PostAcceptHook())
+ return false;
+
+ return true;
+}
+
+bool SMConnection::DoRead() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "DoRead()";
+ while (!read_buffer_.Full()) {
+ char* bytes;
+ int size;
+ if (fd_ == -1) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoRead(): fd_ == -1. Invalid FD. Returning false";
+ return false;
+ }
+ read_buffer_.GetWritablePtr(&bytes, &size);
+ ssize_t bytes_read = 0;
+ if (ssl_) {
+ bytes_read = SSL_read(ssl_, bytes, size);
+ if (bytes_read < 0) {
+ int err = SSL_get_error(ssl_, bytes_read);
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_ACCEPT:
+ case SSL_ERROR_WANT_CONNECT:
+ events_ &= ~EPOLLIN;
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoRead: SSL WANT_XXX: " << err;
+ goto done;
+ default:
+ PrintSslError();
+ goto error_or_close;
+ }
+ }
+ } else {
+ bytes_read = recv(fd_, bytes, size, MSG_DONTWAIT);
+ }
+ int stored_errno = errno;
+ if (bytes_read == -1) {
+ switch (stored_errno) {
+ case EAGAIN:
+ events_ &= ~EPOLLIN;
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EAGAIN while reading";
+ goto done;
+ case EINTR:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EINTR while reading";
+ continue;
+ default:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "While calling recv, got error: "
+ << (ssl_ ? "(ssl error)" : strerror(stored_errno));
+ goto error_or_close;
+ }
+ } else if (bytes_read > 0) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "read " << bytes_read
+ << " bytes";
+ last_read_time_ = time(NULL);
+ // If the protocol hasn't been detected yet, set up the handlers
+ // we'll need.
+ if (!protocol_detected_) {
+ if (!SetupProtocolInterfaces()) {
+ LOG(ERROR) << "Error setting up protocol interfaces.";
+ goto error_or_close;
+ }
+ }
+ read_buffer_.AdvanceWritablePtr(bytes_read);
+ if (!DoConsumeReadData())
+ goto error_or_close;
+ continue;
+ } else { // bytes_read == 0
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "0 bytes read with recv call.";
+ }
+ goto error_or_close;
+ }
+ done:
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "DoRead done!";
+ return true;
+
+ error_or_close:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoRead(): error_or_close. "
+ << "Cleaning up, then returning false";
+ Cleanup("DoRead");
+ return false;
+}
+
+bool SMConnection::DoConsumeReadData() {
+ char* bytes;
+ int size;
+ read_buffer_.GetReadablePtr(&bytes, &size);
+ while (size != 0) {
+ size_t bytes_consumed = sm_interface_->ProcessReadInput(bytes, size);
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "consumed "
+ << bytes_consumed << " bytes";
+ if (bytes_consumed == 0) {
+ break;
+ }
+ read_buffer_.AdvanceReadablePtr(bytes_consumed);
+ if (sm_interface_->MessageFullyRead()) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "HandleRequestFullyRead: Setting EPOLLOUT";
+ HandleResponseFullyRead();
+ events_ |= EPOLLOUT;
+ } else if (sm_interface_->Error()) {
+ LOG(ERROR) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Framer error detected: Setting EPOLLOUT: "
+ << sm_interface_->ErrorAsString();
+ // this causes everything to be closed/cleaned up.
+ events_ |= EPOLLOUT;
+ return false;
+ }
+ read_buffer_.GetReadablePtr(&bytes, &size);
+ }
+ return true;
+}
+
+void SMConnection::HandleResponseFullyRead() { sm_interface_->Cleanup(); }
+
+bool SMConnection::DoWrite() {
+ size_t bytes_sent = 0;
+ int flags = MSG_NOSIGNAL | MSG_DONTWAIT;
+ if (fd_ == -1) {
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoWrite: fd == -1. Returning false.";
+ return false;
+ }
+ if (output_list_.empty()) {
+ VLOG(2) << log_prefix_ << "DoWrite: Output list empty.";
+ if (sm_interface_) {
+ sm_interface_->GetOutput();
+ }
+ if (output_list_.empty()) {
+ events_ &= ~EPOLLOUT;
+ }
+ }
+ while (!output_list_.empty()) {
+ VLOG(2) << log_prefix_
+ << "DoWrite: Items in output list: " << output_list_.size();
+ if (bytes_sent >= max_bytes_sent_per_dowrite_) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << " byte sent >= max bytes sent per write: Setting EPOLLOUT: "
+ << bytes_sent;
+ events_ |= EPOLLOUT;
+ break;
+ }
+ if (sm_interface_ && output_list_.size() < 2) {
+ sm_interface_->GetOutput();
+ }
+ DataFrame* data_frame = output_list_.front();
+ const char* bytes = data_frame->data;
+ int size = data_frame->size;
+ bytes += data_frame->index;
+ size -= data_frame->index;
+ DCHECK_GE(size, 0);
+ if (size <= 0) {
+ output_list_.pop_front();
+ delete data_frame;
+ continue;
+ }
+
+ flags = MSG_NOSIGNAL | MSG_DONTWAIT;
+ // Look for a queue size > 1 because |this| frame is remains on the list
+ // until it has finished sending.
+ if (output_list_.size() > 1) {
+ VLOG(2) << log_prefix_ << "Outlist size: " << output_list_.size()
+ << ": Adding MSG_MORE flag";
+ flags |= MSG_MORE;
+ }
+ VLOG(2) << log_prefix_ << "Attempting to send " << size << " bytes.";
+ ssize_t bytes_written = Send(bytes, size, flags);
+ int stored_errno = errno;
+ if (bytes_written == -1) {
+ switch (stored_errno) {
+ case EAGAIN:
+ events_ &= ~EPOLLOUT;
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EAGAIN while writing";
+ goto done;
+ case EINTR:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Got EINTR while writing";
+ continue;
+ default:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "While calling send, got error: " << stored_errno << ": "
+ << (ssl_ ? "" : strerror(stored_errno));
+ goto error_or_close;
+ }
+ } else if (bytes_written > 0) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "Wrote: " << bytes_written << " bytes";
+ data_frame->index += bytes_written;
+ bytes_sent += bytes_written;
+ continue;
+ } else if (bytes_written == -2) {
+ // -2 handles SSL_ERROR_WANT_* errors
+ events_ &= ~EPOLLOUT;
+ goto done;
+ }
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "0 bytes written with send call.";
+ goto error_or_close;
+ }
+ done:
+ UncorkSocket();
+ return true;
+
+ error_or_close:
+ VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
+ << "DoWrite: error_or_close. Returning false "
+ << "after cleaning up";
+ Cleanup("DoWrite");
+ UncorkSocket();
+ return false;
+}
+
+void SMConnection::Reset() {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Resetting";
+ if (ssl_) {
+ SSL_shutdown(ssl_);
+ PrintSslError();
+ SSL_free(ssl_);
+ PrintSslError();
+ ssl_ = NULL;
+ }
+ if (registered_in_epoll_server_) {
+ epoll_server_->UnregisterFD(fd_);
+ registered_in_epoll_server_ = false;
+ }
+ if (fd_ >= 0) {
+ VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Closing connection";
+ close(fd_);
+ fd_ = -1;
+ }
+ read_buffer_.Clear();
+ initialized_ = false;
+ protocol_detected_ = false;
+ events_ = 0;
+ for (std::list<DataFrame*>::iterator i = output_list_.begin();
+ i != output_list_.end();
+ ++i) {
+ delete *i;
+ }
+ output_list_.clear();
+}
+
+// static
+SMConnection* SMConnection::NewSMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix) {
+ return new SMConnection(
+ epoll_server, ssl_state, memory_cache, acceptor, log_prefix);
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/sm_connection.h b/net/tools/flip_server/sm_connection.h
new file mode 100644
index 0000000..2fe3228
--- /dev/null
+++ b/net/tools/flip_server/sm_connection.h
@@ -0,0 +1,165 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_SM_CONNECTION_H_
+#define NET_TOOLS_FLIP_SERVER_SM_CONNECTION_H_
+
+#include <arpa/inet.h> // in_addr_t
+#include <time.h>
+
+#include <list>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/tools/epoll_server/epoll_server.h"
+#include "net/tools/flip_server/create_listener.h"
+#include "net/tools/flip_server/mem_cache.h"
+#include "net/tools/flip_server/ring_buffer.h"
+#include "net/tools/flip_server/sm_interface.h"
+#include "openssl/ssl.h"
+
+namespace net {
+
+class FlipAcceptor;
+class MemoryCache;
+struct SSLState;
+class SpdySM;
+
+// A frame of data to be sent.
+class DataFrame {
+ public:
+ const char* data;
+ size_t size;
+ bool delete_when_done;
+ size_t index;
+ DataFrame() : data(NULL), size(0), delete_when_done(false), index(0) {}
+ virtual ~DataFrame();
+};
+
+typedef std::list<DataFrame*> OutputList;
+
+class SMConnection : public SMConnectionInterface,
+ public EpollCallbackInterface,
+ public NotifierInterface {
+ public:
+ virtual ~SMConnection();
+
+ static SMConnection* NewSMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix);
+
+ // TODO(mbelshe): Make these private.
+ time_t last_read_time_;
+ std::string server_ip_;
+ std::string server_port_;
+
+ virtual EpollServer* epoll_server() OVERRIDE;
+ OutputList* output_list() { return &output_list_; }
+ MemoryCache* memory_cache() { return memory_cache_; }
+ virtual void ReadyToSend() OVERRIDE;
+ void EnqueueDataFrame(DataFrame* df);
+
+ int fd() const { return fd_; }
+ bool initialized() const { return initialized_; }
+ std::string client_ip() const { return client_ip_; }
+
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl);
+
+ void CorkSocket();
+ void UncorkSocket();
+
+ int Send(const char* data, int len, int flags);
+
+ // EpollCallbackInterface interface.
+ virtual void OnRegistration(EpollServer* eps,
+ int fd,
+ int event_mask) OVERRIDE;
+ virtual void OnModification(int fd, int event_mask) OVERRIDE {}
+ virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE;
+ virtual void OnUnregistration(int fd, bool replaced) OVERRIDE;
+ virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE;
+
+ // NotifierInterface interface.
+ virtual void Notify() OVERRIDE {}
+
+ void Cleanup(const char* cleanup);
+
+ // Flag indicating if we should force spdy on all connections.
+ static bool force_spdy() { return force_spdy_; }
+ static void set_force_spdy(bool value) { force_spdy_ = value; }
+
+ private:
+ // Decide if SPDY was negotiated.
+ bool WasSpdyNegotiated(SpdyMajorVersion* version_negotiated);
+
+ // Initialize the protocol interfaces we'll need for this connection.
+ // Returns true if successful, false otherwise.
+ bool SetupProtocolInterfaces();
+
+ bool DoRead();
+ bool DoWrite();
+ bool DoConsumeReadData();
+ void Reset();
+
+ void HandleEvents();
+ void HandleResponseFullyRead();
+
+ protected:
+ friend std::ostream& operator<<(std::ostream& os, const SMConnection& c) {
+ os << &c << "\n";
+ return os;
+ }
+
+ SMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix);
+
+ private:
+ int fd_;
+ int events_;
+
+ bool registered_in_epoll_server_;
+ bool initialized_;
+ bool protocol_detected_;
+ bool connection_complete_;
+
+ SMConnectionPoolInterface* connection_pool_;
+
+ EpollServer* epoll_server_;
+ SSLState* ssl_state_;
+ MemoryCache* memory_cache_;
+ FlipAcceptor* acceptor_;
+ std::string client_ip_;
+
+ RingBuffer read_buffer_;
+
+ OutputList output_list_;
+ SpdySM* sm_spdy_interface_;
+ SMInterface* sm_http_interface_;
+ SMInterface* sm_streamer_interface_;
+ SMInterface* sm_interface_;
+ std::string log_prefix_;
+
+ size_t max_bytes_sent_per_dowrite_;
+
+ SSL* ssl_;
+
+ static bool force_spdy_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SM_CONNECTION_H_
diff --git a/net/tools/flip_server/sm_interface.h b/net/tools/flip_server/sm_interface.h
new file mode 100644
index 0000000..389c683
--- /dev/null
+++ b/net/tools/flip_server/sm_interface.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_SM_INTERFACE_H_
+#define NET_TOOLS_FLIP_SERVER_SM_INTERFACE_H_
+
+// State Machine Interfaces
+
+#include <string>
+
+#include "net/tools/balsa/balsa_headers.h"
+
+namespace net {
+
+class EpollServer;
+class SMConnectionPoolInterface;
+class SMConnection;
+
+class SMInterface {
+ public:
+ virtual void InitSMInterface(SMInterface* sm_other_interface,
+ int32 server_idx) = 0;
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) = 0;
+ virtual size_t ProcessReadInput(const char* data, size_t len) = 0;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) = 0;
+ virtual void SetStreamID(uint32 stream_id) = 0;
+ virtual bool MessageFullyRead() const = 0;
+ virtual bool Error() const = 0;
+ virtual const char* ErrorAsString() const = 0;
+ virtual void Reset() = 0;
+ virtual void ResetForNewInterface(int32 server_idx) = 0;
+ // ResetForNewConnection is used for interfaces which control SMConnection
+ // objects. When called an interface may put its connection object into
+ // a reusable instance pool. Currently this is what the HttpSM interface
+ // does.
+ virtual void ResetForNewConnection() = 0;
+ virtual void Cleanup() = 0;
+
+ virtual int PostAcceptHook() = 0;
+
+ virtual void NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) = 0;
+ virtual void SendEOF(uint32 stream_id) = 0;
+ virtual void SendErrorNotFound(uint32 stream_id) = 0;
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) = 0;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) = 0;
+ virtual void SendDataFrame(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) = 0;
+ virtual void GetOutput() = 0;
+ virtual void set_is_request() = 0;
+
+ virtual ~SMInterface() {}
+};
+
+class SMConnectionInterface {
+ public:
+ virtual ~SMConnectionInterface() {}
+ virtual void ReadyToSend() = 0;
+ virtual EpollServer* epoll_server() = 0;
+};
+
+class SMConnectionPoolInterface {
+ public:
+ virtual ~SMConnectionPoolInterface() {}
+ virtual void SMConnectionDone(SMConnection* connection) = 0;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SM_INTERFACE_H_
diff --git a/net/tools/flip_server/spdy_interface.cc b/net/tools/flip_server/spdy_interface.cc
new file mode 100644
index 0000000..eec20c0
--- /dev/null
+++ b/net/tools/flip_server/spdy_interface.cc
@@ -0,0 +1,628 @@
+// Copyright (c) 2012 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.
+
+#include "net/tools/flip_server/spdy_interface.h"
+
+#include <algorithm>
+#include <string>
+
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/tools/dump_cache/url_utilities.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/http_interface.h"
+#include "net/tools/flip_server/spdy_util.h"
+
+namespace net {
+
+// static
+std::string SpdySM::forward_ip_header_;
+
+class SpdyFrameDataFrame : public DataFrame {
+ public:
+ explicit SpdyFrameDataFrame(SpdyFrame* spdy_frame) : frame(spdy_frame) {
+ data = spdy_frame->data();
+ size = spdy_frame->size();
+ }
+
+ virtual ~SpdyFrameDataFrame() { delete frame; }
+
+ const SpdyFrame* frame;
+};
+
+SpdySM::SpdySM(SMConnection* connection,
+ SMInterface* sm_http_interface,
+ EpollServer* epoll_server,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ SpdyMajorVersion spdy_version)
+ : buffered_spdy_framer_(new BufferedSpdyFramer(spdy_version, true)),
+ valid_spdy_session_(false),
+ connection_(connection),
+ client_output_list_(connection->output_list()),
+ client_output_ordering_(connection),
+ next_outgoing_stream_id_(2),
+ epoll_server_(epoll_server),
+ acceptor_(acceptor),
+ memory_cache_(memory_cache),
+ close_on_error_(false) {
+ buffered_spdy_framer_->set_visitor(this);
+}
+
+SpdySM::~SpdySM() { }
+
+void SpdySM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Initializing server connection.";
+ connection_->InitSMConnection(connection_pool,
+ sm_interface,
+ epoll_server,
+ fd,
+ server_ip,
+ server_port,
+ remote_ip,
+ use_ssl);
+}
+
+SMInterface* SpdySM::NewConnectionInterface() {
+ SMConnection* server_connection =
+ SMConnection::NewSMConnection(epoll_server_,
+ NULL,
+ memory_cache_,
+ acceptor_,
+ "http_conn: ");
+ if (server_connection == NULL) {
+ LOG(ERROR) << "SpdySM: Could not create server connection";
+ return NULL;
+ }
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Creating new HTTP interface";
+ SMInterface* sm_http_interface =
+ new HttpSM(server_connection, this, memory_cache_, acceptor_);
+ return sm_http_interface;
+}
+
+SMInterface* SpdySM::FindOrMakeNewSMConnectionInterface(
+ const std::string& server_ip,
+ const std::string& server_port) {
+ SMInterface* sm_http_interface;
+ int32 server_idx;
+ if (unused_server_interface_list.empty()) {
+ sm_http_interface = NewConnectionInterface();
+ server_idx = server_interface_list.size();
+ server_interface_list.push_back(sm_http_interface);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "SpdySM: Making new server connection on index: " << server_idx;
+ } else {
+ server_idx = unused_server_interface_list.back();
+ unused_server_interface_list.pop_back();
+ sm_http_interface = server_interface_list.at(server_idx);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reusing connection on "
+ << "index: " << server_idx;
+ }
+
+ sm_http_interface->InitSMInterface(this, server_idx);
+ sm_http_interface->InitSMConnection(NULL,
+ sm_http_interface,
+ epoll_server_,
+ -1,
+ server_ip,
+ server_port,
+ std::string(),
+ false);
+
+ return sm_http_interface;
+}
+
+int SpdySM::SpdyHandleNewStream(SpdyStreamId stream_id,
+ SpdyPriority priority,
+ const SpdyHeaderBlock& headers,
+ std::string& http_data,
+ bool* is_https_scheme) {
+ *is_https_scheme = false;
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSyn(" << stream_id << ")";
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: # headers: " << headers.size();
+
+ SpdyHeaderBlock::const_iterator method = headers.end();
+ SpdyHeaderBlock::const_iterator host = headers.end();
+ SpdyHeaderBlock::const_iterator path = headers.end();
+ SpdyHeaderBlock::const_iterator scheme = headers.end();
+ SpdyHeaderBlock::const_iterator version = headers.end();
+ SpdyHeaderBlock::const_iterator url = headers.end();
+
+ std::string path_string, host_string, version_string;
+
+ if (spdy_version() == SPDY2) {
+ url = headers.find("url");
+ method = headers.find("method");
+ version = headers.find("version");
+ scheme = headers.find("scheme");
+ if (url == headers.end() || method == headers.end() ||
+ version == headers.end() || scheme == headers.end()) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: A mandatory header is "
+ << "missing. Not creating stream";
+ return 0;
+ }
+ // url->second here only ever seems to contain just the path. When this
+ // path contains a query string with a http:// in one of its values,
+ // UrlUtilities::GetUrlPath will fail and always return a / breaking
+ // the request. GetUrlPath assumes the absolute URL is being passed in.
+ path_string = UrlUtilities::GetUrlPath(url->second);
+ host_string = UrlUtilities::GetUrlHost(url->second);
+ version_string = version->second;
+ } else {
+ method = headers.find(":method");
+ host = headers.find(":host");
+ path = headers.find(":path");
+ scheme = headers.find(":scheme");
+ if (method == headers.end() || host == headers.end() ||
+ path == headers.end() || scheme == headers.end()) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: A mandatory header is "
+ << "missing. Not creating stream";
+ return 0;
+ }
+ host_string = host->second;
+ path_string = path->second;
+ version_string = "HTTP/1.1";
+ }
+
+ if (scheme->second.compare("https") == 0) {
+ *is_https_scheme = true;
+ }
+
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second
+ << " " << path_string;
+ std::string filename = EncodeURL(path_string,
+ host_string,
+ method->second);
+ NewStream(stream_id, priority, filename);
+ } else {
+ http_data +=
+ method->second + " " + path_string + " " + version_string + "\r\n";
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Request: " << method->second << " "
+ << path_string << " " << version_string;
+ http_data += "Host: " + (*is_https_scheme ?
+ acceptor_->https_server_ip_ :
+ acceptor_->http_server_ip_) + "\r\n";
+ for (SpdyHeaderBlock::const_iterator i = headers.begin();
+ i != headers.end(); ++i) {
+ if ((i->first.size() > 0 && i->first[0] == ':') ||
+ i->first == "host" ||
+ i == method ||
+ i == host ||
+ i == path ||
+ i == scheme ||
+ i == version ||
+ i == url) {
+ // Ignore the entry.
+ } else {
+ http_data += i->first + ": " + i->second + "\r\n";
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << i->first.c_str() << ":"
+ << i->second.c_str();
+ }
+ }
+ if (forward_ip_header_.length()) {
+ // X-Client-Cluster-IP header
+ http_data += forward_ip_header_ + ": " +
+ connection_->client_ip() + "\r\n";
+ }
+ http_data += "\r\n";
+ }
+
+ VLOG(3) << ACCEPTOR_CLIENT_IDENT << "SpdySM: HTTP Request:\n" << http_data;
+ return 1;
+}
+
+void SpdySM::OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: StreamData(" << stream_id
+ << ", [" << len << "])";
+ StreamToSmif::iterator it = stream_to_smif_.find(stream_id);
+ if (it == stream_to_smif_.end()) {
+ VLOG(2) << "Dropping frame from unknown stream " << stream_id;
+ if (!valid_spdy_session_)
+ close_on_error_ = true;
+ return;
+ }
+
+ SMInterface* interface = it->second;
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY)
+ interface->ProcessWriteInput(data, len);
+}
+
+void SpdySM::OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) {
+ std::string http_data;
+ bool is_https_scheme;
+ int ret = SpdyHandleNewStream(
+ stream_id, priority, headers, http_data, &is_https_scheme);
+ if (!ret) {
+ LOG(ERROR) << "SpdySM: Could not convert spdy into http.";
+ return;
+ }
+ // We've seen a valid looking SYN_STREAM, consider this to have
+ // been a real spdy session.
+ valid_spdy_session_ = true;
+
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ std::string server_ip;
+ std::string server_port;
+ if (is_https_scheme) {
+ server_ip = acceptor_->https_server_ip_;
+ server_port = acceptor_->https_server_port_;
+ } else {
+ server_ip = acceptor_->http_server_ip_;
+ server_port = acceptor_->http_server_port_;
+ }
+ SMInterface* sm_http_interface =
+ FindOrMakeNewSMConnectionInterface(server_ip, server_port);
+ stream_to_smif_[stream_id] = sm_http_interface;
+ sm_http_interface->SetStreamID(stream_id);
+ sm_http_interface->ProcessWriteInput(http_data.c_str(), http_data.size());
+ }
+}
+
+void SpdySM::OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ // TODO(willchan): if there is an error parsing headers, we
+ // should send a RST_STREAM.
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnSynReply(" << stream_id << ")";
+}
+
+void SpdySM::OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnHeaders(" << stream_id << ")";
+}
+
+void SpdySM::OnRstStream(SpdyStreamId stream_id, SpdyRstStreamStatus status) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: OnRstStream(" << stream_id
+ << ")";
+ client_output_ordering_.RemoveStreamId(stream_id);
+}
+
+bool SpdySM::OnUnknownFrame(SpdyStreamId stream_id, int frame_type) {
+ return false;
+}
+
+size_t SpdySM::ProcessReadInput(const char* data, size_t len) {
+ DCHECK(buffered_spdy_framer_);
+ return buffered_spdy_framer_->ProcessInput(data, len);
+}
+
+size_t SpdySM::ProcessWriteInput(const char* data, size_t len) { return 0; }
+
+bool SpdySM::MessageFullyRead() const {
+ DCHECK(buffered_spdy_framer_);
+ return buffered_spdy_framer_->MessageFullyRead();
+}
+
+bool SpdySM::Error() const {
+ DCHECK(buffered_spdy_framer_);
+ return close_on_error_ || buffered_spdy_framer_->HasError();
+}
+
+const char* SpdySM::ErrorAsString() const {
+ DCHECK(Error());
+ DCHECK(buffered_spdy_framer_);
+ return SpdyFramer::ErrorCodeToString(buffered_spdy_framer_->error_code());
+}
+
+void SpdySM::ResetForNewInterface(int32 server_idx) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Reset for new interface: "
+ << "server_idx: " << server_idx;
+ unused_server_interface_list.push_back(server_idx);
+}
+
+void SpdySM::ResetForNewConnection() {
+ // seq_num is not cleared, intentionally.
+ buffered_spdy_framer_.reset();
+ valid_spdy_session_ = false;
+ client_output_ordering_.Reset();
+ next_outgoing_stream_id_ = 2;
+}
+
+// Send a settings frame
+int SpdySM::PostAcceptHook() {
+ // We should have buffered_spdy_framer_ set after reuse
+ DCHECK(buffered_spdy_framer_);
+ SettingsMap settings;
+ settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 100);
+ SpdyFrame* settings_frame = buffered_spdy_framer_->CreateSettings(settings);
+
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending Settings Frame";
+ EnqueueDataFrame(new SpdyFrameDataFrame(settings_frame));
+ return 1;
+}
+
+void SpdySM::NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) {
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+ mci.priority = priority;
+ // TODO(yhirano): The program will crash when
+ // acceptor_->flip_handler_type_ != FLIP_HANDLER_SPDY_SERVER.
+ // It should be fixed or an assertion should be placed.
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_SPDY_SERVER) {
+ if (!memory_cache_->AssignFileData(filename, &mci)) {
+ // error creating new stream.
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Sending ErrorNotFound";
+ SendErrorNotFound(stream_id);
+ } else {
+ AddToOutputOrder(mci);
+ }
+ } else {
+ AddToOutputOrder(mci);
+ }
+}
+
+void SpdySM::AddToOutputOrder(const MemCacheIter& mci) {
+ client_output_ordering_.AddToOutputOrder(mci);
+}
+
+void SpdySM::SendEOF(uint32 stream_id) { SendEOFImpl(stream_id); }
+
+void SpdySM::SendErrorNotFound(uint32 stream_id) {
+ SendErrorNotFoundImpl(stream_id);
+}
+
+size_t SpdySM::SendSynStream(uint32 stream_id, const BalsaHeaders& headers) {
+ return SendSynStreamImpl(stream_id, headers);
+}
+
+size_t SpdySM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
+ return SendSynReplyImpl(stream_id, headers);
+}
+
+void SpdySM::SendDataFrame(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) {
+ SpdyDataFlags spdy_flags = static_cast<SpdyDataFlags>(flags);
+ SendDataFrameImpl(stream_id, data, len, spdy_flags, compress);
+}
+
+void SpdySM::SendEOFImpl(uint32 stream_id) {
+ SendDataFrame(stream_id, NULL, 0, DATA_FLAG_FIN, false);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending EOF: " << stream_id;
+ KillStream(stream_id);
+ stream_to_smif_.erase(stream_id);
+}
+
+void SpdySM::SendErrorNotFoundImpl(uint32 stream_id) {
+ BalsaHeaders my_headers;
+ my_headers.SetFirstlineFromStringPieces("HTTP/1.1", "404", "Not Found");
+ SendSynReplyImpl(stream_id, my_headers);
+ SendDataFrame(stream_id, "wtf?", 4, DATA_FLAG_FIN, false);
+ client_output_ordering_.RemoveStreamId(stream_id);
+}
+
+void SpdySM::KillStream(uint32 stream_id) {
+ client_output_ordering_.RemoveStreamId(stream_id);
+}
+
+void SpdySM::CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers) {
+ for (BalsaHeaders::const_header_lines_iterator hi =
+ headers.header_lines_begin();
+ hi != headers.header_lines_end();
+ ++hi) {
+ // It is illegal to send SPDY headers with empty value or header
+ // names.
+ if (!hi->first.length() || !hi->second.length())
+ continue;
+
+ // Key must be all lower case in SPDY headers.
+ std::string key = hi->first.as_string();
+ std::transform(key.begin(), key.end(), key.begin(), ::tolower);
+ SpdyHeaderBlock::iterator fhi = dest.find(key);
+ if (fhi == dest.end()) {
+ dest[key] = hi->second.as_string();
+ } else {
+ dest[key] = (std::string(fhi->second.data(), fhi->second.size()) + "\0" +
+ std::string(hi->second.data(), hi->second.size()));
+ }
+ }
+
+ // These headers have no value
+ dest.erase("X-Associated-Content"); // TODO(mbelshe): case-sensitive
+ dest.erase("X-Original-Url"); // TODO(mbelshe): case-sensitive
+}
+
+size_t SpdySM::SendSynStreamImpl(uint32 stream_id,
+ const BalsaHeaders& headers) {
+ SpdyHeaderBlock block;
+ CopyHeaders(block, headers);
+ if (spdy_version() == SPDY2) {
+ block["method"] = headers.request_method().as_string();
+ if (!headers.HasHeader("version"))
+ block["version"] = headers.request_version().as_string();
+ if (headers.HasHeader("X-Original-Url")) {
+ std::string original_url =
+ headers.GetHeader("X-Original-Url").as_string();
+ block["url"] = UrlUtilities::GetUrlPath(original_url);
+ } else {
+ block["url"] = headers.request_uri().as_string();
+ }
+ } else {
+ block[":method"] = headers.request_method().as_string();
+ block[":version"] = headers.request_version().as_string();
+ if (headers.HasHeader("X-Original-Url")) {
+ std::string original_url =
+ headers.GetHeader("X-Original-Url").as_string();
+ block[":path"] = UrlUtilities::GetUrlPath(original_url);
+ block[":host"] = UrlUtilities::GetUrlPath(original_url);
+ } else {
+ block[":path"] = headers.request_uri().as_string();
+ if (block.find("host") != block.end()) {
+ block[":host"] = headers.GetHeader("Host").as_string();
+ block.erase("host");
+ }
+ }
+ }
+
+ DCHECK(buffered_spdy_framer_);
+ SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynStream(
+ stream_id, 0, 0, CONTROL_FLAG_NONE, &block);
+ size_t df_size = fsrcf->size();
+ EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
+
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynStreamheader "
+ << stream_id;
+ return df_size;
+}
+
+size_t SpdySM::SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ SpdyHeaderBlock block;
+ CopyHeaders(block, headers);
+ if (spdy_version() == SPDY2) {
+ block["status"] = headers.response_code().as_string() + " " +
+ headers.response_reason_phrase().as_string();
+ block["version"] = headers.response_version().as_string();
+ } else {
+ block[":status"] = headers.response_code().as_string() + " " +
+ headers.response_reason_phrase().as_string();
+ block[":version"] = headers.response_version().as_string();
+ }
+
+ DCHECK(buffered_spdy_framer_);
+ SpdyFrame* fsrcf = buffered_spdy_framer_->CreateSynReply(
+ stream_id, CONTROL_FLAG_NONE, &block);
+ size_t df_size = fsrcf->size();
+ EnqueueDataFrame(new SpdyFrameDataFrame(fsrcf));
+
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending SynReplyheader "
+ << stream_id;
+ return df_size;
+}
+
+void SpdySM::SendDataFrameImpl(uint32 stream_id,
+ const char* data,
+ int64 len,
+ SpdyDataFlags flags,
+ bool compress) {
+ DCHECK(buffered_spdy_framer_);
+ // TODO(mbelshe): We can't compress here - before going into the
+ // priority queue. Compression needs to be done
+ // with late binding.
+ if (len == 0) {
+ SpdyFrame* fdf =
+ buffered_spdy_framer_->CreateDataFrame(stream_id, data, len, flags);
+ EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
+ return;
+ }
+
+ // Chop data frames into chunks so that one stream can't monopolize the
+ // output channel.
+ while (len > 0) {
+ int64 size = std::min(len, static_cast<int64>(kSpdySegmentSize));
+ SpdyDataFlags chunk_flags = flags;
+
+ // If we chunked this block, and the FIN flag was set, there is more
+ // data coming. So, remove the flag.
+ if ((size < len) && (flags & DATA_FLAG_FIN))
+ chunk_flags = static_cast<SpdyDataFlags>(chunk_flags & ~DATA_FLAG_FIN);
+
+ SpdyFrame* fdf = buffered_spdy_framer_->CreateDataFrame(
+ stream_id, data, size, chunk_flags);
+ EnqueueDataFrame(new SpdyFrameDataFrame(fdf));
+
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: Sending data frame "
+ << stream_id << " [" << size << "] shrunk to "
+ << (fdf->size() - kSpdyOverhead) << ", flags=" << flags;
+
+ data += size;
+ len -= size;
+ }
+}
+
+void SpdySM::EnqueueDataFrame(DataFrame* df) {
+ connection_->EnqueueDataFrame(df);
+}
+
+void SpdySM::GetOutput() {
+ while (client_output_list_->size() < 2) {
+ MemCacheIter* mci = client_output_ordering_.GetIter();
+ if (mci == NULL) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "SpdySM: GetOutput: nothing to output!?";
+ return;
+ }
+ if (!mci->transformed_header) {
+ mci->transformed_header = true;
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput transformed "
+ << "header stream_id: [" << mci->stream_id << "]";
+ if ((mci->stream_id % 2) == 0) {
+ // this is a server initiated stream.
+ // Ideally, we'd do a 'syn-push' here, instead of a syn-reply.
+ BalsaHeaders headers;
+ headers.CopyFrom(*(mci->file_data->headers()));
+ headers.ReplaceOrAppendHeader("status", "200");
+ headers.ReplaceOrAppendHeader("version", "http/1.1");
+ headers.SetRequestFirstlineFromStringPieces(
+ "PUSH", mci->file_data->filename(), "");
+ mci->bytes_sent = SendSynStream(mci->stream_id, headers);
+ } else {
+ BalsaHeaders headers;
+ headers.CopyFrom(*(mci->file_data->headers()));
+ mci->bytes_sent = SendSynReply(mci->stream_id, headers);
+ }
+ return;
+ }
+ if (mci->body_bytes_consumed >= mci->file_data->body().size()) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput "
+ << "remove_stream_id: [" << mci->stream_id << "]";
+ SendEOF(mci->stream_id);
+ return;
+ }
+ size_t num_to_write =
+ mci->file_data->body().size() - mci->body_bytes_consumed;
+ if (num_to_write > mci->max_segment_size)
+ num_to_write = mci->max_segment_size;
+
+ bool should_compress = false;
+ if (!mci->file_data->headers()->HasHeader("content-encoding")) {
+ if (mci->file_data->headers()->HasHeader("content-type")) {
+ std::string content_type =
+ mci->file_data->headers()->GetHeader("content-type").as_string();
+ if (content_type.find("image") == content_type.npos)
+ should_compress = true;
+ }
+ }
+
+ SendDataFrame(mci->stream_id,
+ mci->file_data->body().data() + mci->body_bytes_consumed,
+ num_to_write,
+ 0,
+ should_compress);
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "SpdySM: GetOutput SendDataFrame["
+ << mci->stream_id << "]: " << num_to_write;
+ mci->body_bytes_consumed += num_to_write;
+ mci->bytes_sent += num_to_write;
+ }
+}
+
+void SpdySM::CreateFramer(SpdyMajorVersion spdy_version) {
+ DCHECK(!buffered_spdy_framer_);
+ buffered_spdy_framer_.reset(new BufferedSpdyFramer(spdy_version, true));
+ buffered_spdy_framer_->set_visitor(this);
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/spdy_interface.h b/net/tools/flip_server/spdy_interface.h
new file mode 100644
index 0000000..2174274
--- /dev/null
+++ b/net/tools/flip_server/spdy_interface.h
@@ -0,0 +1,222 @@
+// Copyright (c) 2012 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_TOOLS_FLIP_SERVER_SPDY_INTERFACE_H_
+#define NET_TOOLS_FLIP_SERVER_SPDY_INTERFACE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/tools/balsa/balsa_headers.h"
+#include "net/tools/balsa/balsa_visitor_interface.h"
+#include "net/tools/flip_server/output_ordering.h"
+#include "net/tools/flip_server/sm_connection.h"
+#include "net/tools/flip_server/sm_interface.h"
+
+namespace net {
+
+class FlipAcceptor;
+class MemoryCache;
+
+class SpdySM : public BufferedSpdyFramerVisitorInterface, public SMInterface {
+ public:
+ SpdySM(SMConnection* connection,
+ SMInterface* sm_http_interface,
+ EpollServer* epoll_server,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ SpdyMajorVersion spdy_version);
+ virtual ~SpdySM();
+
+ virtual void InitSMInterface(SMInterface* sm_http_interface,
+ int32 server_idx) OVERRIDE {}
+
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) OVERRIDE;
+
+ // Create new SPDY framer after reusing SpdySM and negotiating new version
+ void CreateFramer(SpdyMajorVersion spdy_version);
+
+ private:
+ virtual void set_is_request() OVERRIDE {}
+ SMInterface* NewConnectionInterface();
+ // virtual for tests
+ virtual SMInterface* FindOrMakeNewSMConnectionInterface(
+ const std::string& server_ip,
+ const std::string& server_port);
+ int SpdyHandleNewStream(SpdyStreamId stream_id,
+ SpdyPriority priority,
+ const SpdyHeaderBlock& headers,
+ std::string& http_data,
+ bool* is_https_scheme);
+
+ // BufferedSpdyFramerVisitorInterface:
+ virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE {}
+ virtual void OnStreamError(SpdyStreamId stream_id,
+ const std::string& description) OVERRIDE {}
+ // Called after all the header data for SYN_STREAM control frame is received.
+ virtual void OnSynStream(SpdyStreamId stream_id,
+ SpdyStreamId associated_stream_id,
+ SpdyPriority priority,
+ bool fin,
+ bool unidirectional,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+
+ // Called after all the header data for SYN_REPLY control frame is received.
+ virtual void OnSynReply(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+
+ // Called after all the header data for HEADERS control frame is received.
+ virtual void OnHeaders(SpdyStreamId stream_id,
+ bool fin,
+ const SpdyHeaderBlock& headers) OVERRIDE;
+
+ // Called when data frame header is received.
+ virtual void OnDataFrameHeader(SpdyStreamId stream_id,
+ size_t length,
+ bool fin) OVERRIDE {}
+
+ // Called when data is received.
+ // |stream_id| The stream receiving data.
+ // |data| A buffer containing the data received.
+ // |len| The length of the data buffer.
+ // When the other side has finished sending data on this stream,
+ // this method will be called with a zero-length buffer.
+ virtual void OnStreamFrameData(SpdyStreamId stream_id,
+ const char* data,
+ size_t len,
+ bool fin) OVERRIDE;
+
+ // Called when a SETTINGS frame is received.
+ // |clear_persisted| True if the respective flag is set on the SETTINGS frame.
+ virtual void OnSettings(bool clear_persisted) OVERRIDE {}
+
+ // Called when an individual setting within a SETTINGS frame has been parsed
+ // and validated.
+ virtual void OnSetting(SpdySettingsIds id,
+ uint8 flags,
+ uint32 value) OVERRIDE {}
+
+ // Called when a PING frame has been parsed.
+ virtual void OnPing(SpdyPingId unique_id, bool is_ack) OVERRIDE {}
+
+ // Called when a RST_STREAM frame has been parsed.
+ virtual void OnRstStream(SpdyStreamId stream_id,
+ SpdyRstStreamStatus status) OVERRIDE;
+
+ // Called when a GOAWAY frame has been parsed.
+ virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+ SpdyGoAwayStatus status) OVERRIDE {}
+
+ // Called when a WINDOW_UPDATE frame has been parsed.
+ virtual void OnWindowUpdate(SpdyStreamId stream_id,
+ uint32 delta_window_size) OVERRIDE {}
+
+ // Called when a PUSH_PROMISE frame has been parsed.
+ virtual void OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id,
+ const SpdyHeaderBlock& headers) OVERRIDE {}
+
+ virtual bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) OVERRIDE;
+
+ public:
+ virtual size_t ProcessReadInput(const char* data, size_t len) OVERRIDE;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) OVERRIDE;
+ virtual bool MessageFullyRead() const OVERRIDE;
+ virtual void SetStreamID(uint32 stream_id) OVERRIDE {}
+ virtual bool Error() const OVERRIDE;
+ virtual const char* ErrorAsString() const OVERRIDE;
+ virtual void Reset() OVERRIDE {}
+ virtual void ResetForNewInterface(int32 server_idx) OVERRIDE;
+ virtual void ResetForNewConnection() OVERRIDE;
+ // SMInterface's Cleanup is currently only called by SMConnection after a
+ // protocol message as been fully read. Spdy's SMInterface does not need
+ // to do any cleanup at this time.
+ // TODO(klindsay) This method is probably not being used properly and
+ // some logic review and method renaming is probably in order.
+ virtual void Cleanup() OVERRIDE {}
+ // Send a settings frame
+ virtual int PostAcceptHook() OVERRIDE;
+ virtual void NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) OVERRIDE;
+ void AddToOutputOrder(const MemCacheIter& mci);
+ virtual void SendEOF(uint32 stream_id) OVERRIDE;
+ virtual void SendErrorNotFound(uint32 stream_id) OVERRIDE;
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual void SendDataFrame(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) OVERRIDE;
+ BufferedSpdyFramer* spdy_framer() { return buffered_spdy_framer_.get(); }
+
+ const OutputOrdering& output_ordering() const {
+ return client_output_ordering_;
+ }
+
+ static std::string forward_ip_header() { return forward_ip_header_; }
+ static void set_forward_ip_header(const std::string& value) {
+ forward_ip_header_ = value;
+ }
+ SpdyMajorVersion spdy_version() const {
+ DCHECK(buffered_spdy_framer_);
+ return buffered_spdy_framer_->protocol_version();
+ }
+
+ private:
+ void SendEOFImpl(uint32 stream_id);
+ void SendErrorNotFoundImpl(uint32 stream_id);
+ void KillStream(uint32 stream_id);
+ void CopyHeaders(SpdyHeaderBlock& dest, const BalsaHeaders& headers);
+ size_t SendSynStreamImpl(uint32 stream_id, const BalsaHeaders& headers);
+ size_t SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers);
+ void SendDataFrameImpl(uint32 stream_id,
+ const char* data,
+ int64 len,
+ SpdyDataFlags flags,
+ bool compress);
+ void EnqueueDataFrame(DataFrame* df);
+ virtual void GetOutput() OVERRIDE;
+
+ private:
+ scoped_ptr<BufferedSpdyFramer> buffered_spdy_framer_;
+ bool valid_spdy_session_; // True if we have seen valid data on this session.
+ // Use this to fail fast when junk is sent to our
+ // port.
+
+ SMConnection* connection_;
+ OutputList* client_output_list_;
+ OutputOrdering client_output_ordering_;
+ uint32 next_outgoing_stream_id_;
+ EpollServer* epoll_server_;
+ FlipAcceptor* acceptor_;
+ MemoryCache* memory_cache_;
+ std::vector<SMInterface*> server_interface_list;
+ std::vector<int32> unused_server_interface_list;
+ typedef std::map<uint32, SMInterface*> StreamToSmif;
+ StreamToSmif stream_to_smif_;
+ bool close_on_error_;
+
+ static std::string forward_ip_header_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SPDY_INTERFACE_H_
diff --git a/net/tools/flip_server/spdy_interface_test.cc b/net/tools/flip_server/spdy_interface_test.cc
new file mode 100644
index 0000000..9e66d0c
--- /dev/null
+++ b/net/tools/flip_server/spdy_interface_test.cc
@@ -0,0 +1,887 @@
+// Copyright 2013 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.
+
+#include "net/tools/flip_server/spdy_interface.h"
+
+#include <list>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/tools/balsa/balsa_enums.h"
+#include "net/tools/balsa/balsa_headers.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/flip_test_utils.h"
+#include "net/tools/flip_server/mem_cache.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+using ::base::StringPiece;
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::Values;
+
+namespace {
+
+struct StringSaver {
+ public:
+ StringSaver() : data(NULL), size(0) {}
+ void Save() { string = std::string(data, size); }
+
+ const char* data;
+ size_t size;
+ std::string string;
+};
+
+class SpdyFramerVisitor : public BufferedSpdyFramerVisitorInterface {
+ public:
+ virtual ~SpdyFramerVisitor() {}
+ MOCK_METHOD1(OnError, void(SpdyFramer::SpdyError));
+ MOCK_METHOD2(OnStreamError, void(SpdyStreamId, const std::string&));
+ MOCK_METHOD6(OnSynStream,
+ void(SpdyStreamId,
+ SpdyStreamId,
+ SpdyPriority,
+ bool,
+ bool,
+ const SpdyHeaderBlock&));
+ MOCK_METHOD3(OnSynReply, void(SpdyStreamId, bool, const SpdyHeaderBlock&));
+ MOCK_METHOD3(OnHeaders, void(SpdyStreamId, bool, const SpdyHeaderBlock&));
+ MOCK_METHOD3(OnDataFrameHeader, void(SpdyStreamId, size_t, bool));
+ MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId,
+ const char*,
+ size_t,
+ bool));
+ MOCK_METHOD1(OnSettings, void(bool clear_persisted));
+ MOCK_METHOD3(OnSetting, void(SpdySettingsIds, uint8, uint32));
+ MOCK_METHOD2(OnPing, void(SpdyPingId unique_id, bool is_ack));
+ MOCK_METHOD2(OnRstStream, void(SpdyStreamId, SpdyRstStreamStatus));
+ MOCK_METHOD2(OnGoAway, void(SpdyStreamId, SpdyGoAwayStatus));
+ MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId, uint32));
+ MOCK_METHOD3(OnPushPromise,
+ void(SpdyStreamId, SpdyStreamId, const SpdyHeaderBlock&));
+ MOCK_METHOD2(OnUnknownFrame, bool(SpdyStreamId stream_id, int frame_type));
+};
+
+class FakeSMConnection : public SMConnection {
+ public:
+ FakeSMConnection(EpollServer* epoll_server,
+ SSLState* ssl_state,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ std::string log_prefix)
+ : SMConnection(epoll_server,
+ ssl_state,
+ memory_cache,
+ acceptor,
+ log_prefix) {}
+
+ MOCK_METHOD0(Cleanup, void());
+ MOCK_METHOD8(InitSMConnection,
+ void(SMConnectionPoolInterface*,
+ SMInterface*,
+ EpollServer*,
+ int,
+ std::string,
+ std::string,
+ std::string,
+ bool));
+};
+
+// This class is almost SpdySM, except one function.
+// This class is the test target of tests in this file.
+class TestSpdySM : public SpdySM {
+ public:
+ virtual ~TestSpdySM() {}
+ TestSpdySM(SMConnection* connection,
+ SMInterface* sm_http_interface,
+ EpollServer* epoll_server,
+ MemoryCache* memory_cache,
+ FlipAcceptor* acceptor,
+ SpdyMajorVersion version)
+ : SpdySM(connection,
+ sm_http_interface,
+ epoll_server,
+ memory_cache,
+ acceptor,
+ version) {}
+
+ MOCK_METHOD2(FindOrMakeNewSMConnectionInterface,
+ SMInterface*(const std::string&, const std::string&));
+};
+
+class SpdySMTestBase : public ::testing::TestWithParam<SpdyMajorVersion> {
+ public:
+ explicit SpdySMTestBase(FlipHandlerType type) {
+ SSLState* ssl_state = NULL;
+ mock_another_interface_.reset(new MockSMInterface);
+ memory_cache_.reset(new MemoryCache);
+ acceptor_.reset(new FlipAcceptor(type,
+ "127.0.0.1",
+ "8941",
+ "ssl_cert_filename",
+ "ssl_key_filename",
+ "127.0.0.1",
+ "8942",
+ "127.0.0.1",
+ "8943",
+ 1,
+ 0,
+ true,
+ 1,
+ false,
+ true,
+ NULL));
+ epoll_server_.reset(new EpollServer);
+ connection_.reset(new FakeSMConnection(epoll_server_.get(),
+ ssl_state,
+ memory_cache_.get(),
+ acceptor_.get(),
+ "log_prefix"));
+
+ interface_.reset(new TestSpdySM(connection_.get(),
+ mock_another_interface_.get(),
+ epoll_server_.get(),
+ memory_cache_.get(),
+ acceptor_.get(),
+ GetParam()));
+
+ spdy_framer_.reset(new BufferedSpdyFramer(GetParam(), true));
+ spdy_framer_visitor_.reset(new SpdyFramerVisitor);
+ spdy_framer_->set_visitor(spdy_framer_visitor_.get());
+ }
+
+ virtual ~SpdySMTestBase() {
+ if (acceptor_->listen_fd_ >= 0) {
+ epoll_server_->UnregisterFD(acceptor_->listen_fd_);
+ close(acceptor_->listen_fd_);
+ acceptor_->listen_fd_ = -1;
+ }
+ OutputList& output_list = *connection_->output_list();
+ for (OutputList::const_iterator i = output_list.begin();
+ i != output_list.end();
+ ++i) {
+ delete *i;
+ }
+ output_list.clear();
+ }
+
+ bool HasStream(uint32 stream_id) {
+ return interface_->output_ordering().ExistsInPriorityMaps(stream_id);
+ }
+
+ protected:
+ scoped_ptr<MockSMInterface> mock_another_interface_;
+ scoped_ptr<MemoryCache> memory_cache_;
+ scoped_ptr<FlipAcceptor> acceptor_;
+ scoped_ptr<EpollServer> epoll_server_;
+ scoped_ptr<FakeSMConnection> connection_;
+ scoped_ptr<TestSpdySM> interface_;
+ scoped_ptr<BufferedSpdyFramer> spdy_framer_;
+ scoped_ptr<SpdyFramerVisitor> spdy_framer_visitor_;
+};
+
+class SpdySMProxyTest : public SpdySMTestBase {
+ public:
+ SpdySMProxyTest() : SpdySMTestBase(FLIP_HANDLER_PROXY) {}
+ virtual ~SpdySMProxyTest() {}
+};
+
+class SpdySMServerTest : public SpdySMTestBase {
+ public:
+ SpdySMServerTest() : SpdySMTestBase(FLIP_HANDLER_SPDY_SERVER) {}
+ virtual ~SpdySMServerTest() {}
+};
+
+INSTANTIATE_TEST_CASE_P(SpdySMProxyTest,
+ SpdySMProxyTest,
+ Values(SPDY2, SPDY3, SPDY4));
+INSTANTIATE_TEST_CASE_P(SpdySMServerTest, SpdySMServerTest, Values(SPDY2));
+
+TEST_P(SpdySMProxyTest, InitSMConnection) {
+ {
+ InSequence s;
+ EXPECT_CALL(*connection_, InitSMConnection(_, _, _, _, _, _, _, _));
+ }
+ interface_->InitSMConnection(
+ NULL, NULL, epoll_server_.get(), -1, "", "", "", false);
+}
+
+TEST_P(SpdySMProxyTest, OnSynStream_SPDY2) {
+ if (GetParam() != SPDY2) {
+ // This test case is for SPDY2.
+ return;
+ }
+ BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
+ scoped_ptr<MockSMInterface> mock_interface(new MockSMInterface);
+ uint32 stream_id = 92;
+ uint32 associated_id = 43;
+ std::string expected = "GET /path HTTP/1.0\r\n"
+ "Host: 127.0.0.1\r\n"
+ "hoge: fuga\r\n\r\n";
+ SpdyHeaderBlock block;
+ block["method"] = "GET";
+ block["url"] = "/path";
+ block["scheme"] = "http";
+ block["version"] = "HTTP/1.0";
+ block["hoge"] = "fuga";
+ StringSaver saver;
+ {
+ InSequence s;
+ EXPECT_CALL(*interface_, FindOrMakeNewSMConnectionInterface(_, _))
+ .WillOnce(Return(mock_interface.get()));
+ EXPECT_CALL(*mock_interface, SetStreamID(stream_id));
+ EXPECT_CALL(*mock_interface, ProcessWriteInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&saver.data),
+ SaveArg<1>(&saver.size),
+ InvokeWithoutArgs(&saver, &StringSaver::Save),
+ Return(0)));
+ }
+ visitor->OnSynStream(stream_id, associated_id, 0, false, false, block);
+ ASSERT_EQ(expected, saver.string);
+}
+
+TEST_P(SpdySMProxyTest, OnSynStream) {
+ if (GetParam() == SPDY2) {
+ // This test case is not for SPDY2.
+ return;
+ }
+ BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
+ scoped_ptr<MockSMInterface> mock_interface(new MockSMInterface);
+ uint32 stream_id = 92;
+ uint32 associated_id = 43;
+ std::string expected = "GET /path HTTP/1.1\r\n"
+ "Host: 127.0.0.1\r\n"
+ "foo: bar\r\n\r\n";
+ SpdyHeaderBlock block;
+ block[":method"] = "GET";
+ block[":host"] = "www.example.com";
+ block[":path"] = "/path";
+ block[":scheme"] = "http";
+ block["foo"] = "bar";
+ StringSaver saver;
+ {
+ InSequence s;
+ EXPECT_CALL(*interface_,
+ FindOrMakeNewSMConnectionInterface(_, _))
+ .WillOnce(Return(mock_interface.get()));
+ EXPECT_CALL(*mock_interface, SetStreamID(stream_id));
+ EXPECT_CALL(*mock_interface, ProcessWriteInput(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&saver.data),
+ SaveArg<1>(&saver.size),
+ InvokeWithoutArgs(&saver, &StringSaver::Save),
+ Return(0)));
+ }
+ visitor->OnSynStream(stream_id, associated_id, 0, false, false, block);
+ ASSERT_EQ(expected, saver.string);
+}
+
+TEST_P(SpdySMProxyTest, OnStreamFrameData_SPDY2) {
+ if (GetParam() != SPDY2) {
+ // This test case is for SPDY2.
+ return;
+ }
+ BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
+ scoped_ptr<MockSMInterface> mock_interface(new MockSMInterface);
+ uint32 stream_id = 92;
+ uint32 associated_id = 43;
+ SpdyHeaderBlock block;
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+
+ scoped_ptr<SpdyFrame> frame(spdy_framer_->CreatePingFrame(12, false));
+ block["method"] = "GET";
+ block["url"] = "http://www.example.com/path";
+ block["scheme"] = "http";
+ block["version"] = "HTTP/1.0";
+ {
+ InSequence s;
+ EXPECT_CALL(*interface_, FindOrMakeNewSMConnectionInterface(_, _))
+ .WillOnce(Return(mock_interface.get()));
+ EXPECT_CALL(*mock_interface, SetStreamID(stream_id));
+ EXPECT_CALL(*mock_interface, ProcessWriteInput(_, _)).Times(1);
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*mock_interface,
+ ProcessWriteInput(frame->data(), frame->size())).Times(1);
+ }
+
+ visitor->OnSynStream(stream_id, associated_id, 0, false, false, block);
+ checkpoint.Call(0);
+ visitor->OnStreamFrameData(stream_id, frame->data(), frame->size(), true);
+}
+
+TEST_P(SpdySMProxyTest, OnStreamFrameData) {
+ if (GetParam() == SPDY2) {
+ // This test case is not for SPDY2.
+ return;
+ }
+ BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
+ scoped_ptr<MockSMInterface> mock_interface(new MockSMInterface);
+ uint32 stream_id = 92;
+ uint32 associated_id = 43;
+ SpdyHeaderBlock block;
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+
+ scoped_ptr<SpdyFrame> frame(spdy_framer_->CreatePingFrame(12, false));
+ block[":method"] = "GET";
+ block[":host"] = "www.example.com";
+ block[":path"] = "/path";
+ block[":scheme"] = "http";
+ block["foo"] = "bar";
+ {
+ InSequence s;
+ EXPECT_CALL(*interface_,
+ FindOrMakeNewSMConnectionInterface(_, _))
+ .WillOnce(Return(mock_interface.get()));
+ EXPECT_CALL(*mock_interface, SetStreamID(stream_id));
+ EXPECT_CALL(*mock_interface, ProcessWriteInput(_, _)).Times(1);
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*mock_interface,
+ ProcessWriteInput(frame->data(), frame->size())).Times(1);
+ }
+
+ visitor->OnSynStream(stream_id, associated_id, 0, false, false, block);
+ checkpoint.Call(0);
+ visitor->OnStreamFrameData(stream_id, frame->data(), frame->size(), true);
+}
+
+TEST_P(SpdySMProxyTest, OnRstStream) {
+ BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
+ uint32 stream_id = 82;
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+
+ {
+ BalsaHeaders headers;
+ std::string filename = "foobar";
+ memory_cache_->InsertFile(&headers, filename, "");
+ mci.file_data = memory_cache_->GetFileData(filename);
+ }
+
+ interface_->AddToOutputOrder(mci);
+ ASSERT_TRUE(HasStream(stream_id));
+ visitor->OnRstStream(stream_id, RST_STREAM_INVALID);
+ ASSERT_FALSE(HasStream(stream_id));
+}
+
+TEST_P(SpdySMProxyTest, ProcessReadInput) {
+ ASSERT_EQ(SpdyFramer::SPDY_RESET, interface_->spdy_framer()->state());
+ interface_->ProcessReadInput("", 1);
+ ASSERT_EQ(SpdyFramer::SPDY_READING_COMMON_HEADER,
+ interface_->spdy_framer()->state());
+}
+
+TEST_P(SpdySMProxyTest, ResetForNewConnection) {
+ uint32 stream_id = 13;
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+ // incomplete input
+ const char input[] = {'\0', '\0', '\0'};
+
+ {
+ BalsaHeaders headers;
+ std::string filename = "foobar";
+ memory_cache_->InsertFile(&headers, filename, "");
+ mci.file_data = memory_cache_->GetFileData(filename);
+ }
+
+ interface_->AddToOutputOrder(mci);
+ ASSERT_TRUE(HasStream(stream_id));
+ interface_->ProcessReadInput(input, sizeof(input));
+ ASSERT_NE(SpdyFramer::SPDY_RESET, interface_->spdy_framer()->state());
+
+ interface_->ResetForNewConnection();
+ ASSERT_FALSE(HasStream(stream_id));
+ ASSERT_TRUE(interface_->spdy_framer() == NULL);
+}
+
+TEST_P(SpdySMProxyTest, CreateFramer) {
+ interface_->ResetForNewConnection();
+ interface_->CreateFramer(SPDY2);
+ ASSERT_TRUE(interface_->spdy_framer() != NULL);
+ ASSERT_EQ(interface_->spdy_version(), SPDY2);
+
+ interface_->ResetForNewConnection();
+ interface_->CreateFramer(SPDY3);
+ ASSERT_TRUE(interface_->spdy_framer() != NULL);
+ ASSERT_EQ(interface_->spdy_version(), SPDY3);
+}
+
+TEST_P(SpdySMProxyTest, PostAcceptHook) {
+ interface_->PostAcceptHook();
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*spdy_framer_visitor_, OnSettings(false));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnSetting(SETTINGS_MAX_CONCURRENT_STREAMS, 0u, 100u));
+ }
+ spdy_framer_->ProcessInput(df->data, df->size);
+}
+
+TEST_P(SpdySMProxyTest, NewStream) {
+ // TODO(yhirano): SpdySM::NewStream leads to crash when
+ // acceptor_->flip_handler_type_ != FLIP_HANDLER_SPDY_SERVER.
+ // It should be fixed though I don't know the solution now.
+}
+
+TEST_P(SpdySMProxyTest, AddToOutputOrder) {
+ uint32 stream_id = 13;
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+
+ {
+ BalsaHeaders headers;
+ std::string filename = "foobar";
+ memory_cache_->InsertFile(&headers, filename, "");
+ mci.file_data = memory_cache_->GetFileData(filename);
+ }
+
+ interface_->AddToOutputOrder(mci);
+ ASSERT_TRUE(HasStream(stream_id));
+}
+
+TEST_P(SpdySMProxyTest, SendErrorNotFound_SPDY2) {
+ if (GetParam() != SPDY2) {
+ // This test is for SPDY2.
+ return;
+ }
+ uint32 stream_id = 82;
+ SpdyHeaderBlock actual_header_block;
+ const char* actual_data;
+ size_t actual_size;
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+
+ interface_->SendErrorNotFound(stream_id);
+
+ ASSERT_EQ(2u, connection_->output_list()->size());
+
+ {
+ InSequence s;
+ if (GetParam() < SPDY4) {
+ EXPECT_CALL(*spdy_framer_visitor_, OnSynReply(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ } else {
+ EXPECT_CALL(*spdy_framer_visitor_, OnHeaders(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ }
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnDataFrameHeader(stream_id, _, true));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, _, _, false)).Times(1)
+ .WillOnce(DoAll(SaveArg<1>(&actual_data),
+ SaveArg<2>(&actual_size)));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, NULL, 0, true)).Times(1);
+ }
+
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+ checkpoint.Call(0);
+ df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+
+ ASSERT_EQ(2, spdy_framer_->frames_received());
+ ASSERT_EQ(2u, actual_header_block.size());
+ ASSERT_EQ("404 Not Found", actual_header_block["status"]);
+ ASSERT_EQ("HTTP/1.1", actual_header_block["version"]);
+ ASSERT_EQ("wtf?", StringPiece(actual_data, actual_size));
+}
+
+TEST_P(SpdySMProxyTest, SendErrorNotFound) {
+ if (GetParam() == SPDY2) {
+ // This test is not for SPDY2.
+ return;
+ }
+ uint32 stream_id = 82;
+ SpdyHeaderBlock actual_header_block;
+ const char* actual_data;
+ size_t actual_size;
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+
+ interface_->SendErrorNotFound(stream_id);
+
+ ASSERT_EQ(2u, connection_->output_list()->size());
+
+ {
+ InSequence s;
+ if (GetParam() < SPDY4) {
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnSynReply(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ } else {
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnHeaders(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ }
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnDataFrameHeader(stream_id, _, true));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, _, _, false)).Times(1)
+ .WillOnce(DoAll(SaveArg<1>(&actual_data),
+ SaveArg<2>(&actual_size)));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, NULL, 0, true)).Times(1);
+ }
+
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+ checkpoint.Call(0);
+ df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+
+ ASSERT_EQ(2, spdy_framer_->frames_received());
+ ASSERT_EQ(2u, actual_header_block.size());
+ ASSERT_EQ("404 Not Found", actual_header_block[":status"]);
+ ASSERT_EQ("HTTP/1.1", actual_header_block[":version"]);
+ ASSERT_EQ("wtf?", StringPiece(actual_data, actual_size));
+}
+
+TEST_P(SpdySMProxyTest, SendSynStream_SPDY2) {
+ if (GetParam() != SPDY2) {
+ // This test is for SPDY2.
+ return;
+ }
+ uint32 stream_id = 82;
+ BalsaHeaders headers;
+ SpdyHeaderBlock actual_header_block;
+ headers.AppendHeader("key1", "value1");
+ headers.SetRequestFirstlineFromStringPieces("GET", "/path", "HTTP/1.0");
+
+ interface_->SendSynStream(stream_id, headers);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnSynStream(stream_id, 0, _, false, false, _))
+ .WillOnce(SaveArg<5>(&actual_header_block));
+ }
+
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ(1, spdy_framer_->frames_received());
+ ASSERT_EQ(4u, actual_header_block.size());
+ ASSERT_EQ("GET", actual_header_block["method"]);
+ ASSERT_EQ("HTTP/1.0", actual_header_block["version"]);
+ ASSERT_EQ("/path", actual_header_block["url"]);
+ ASSERT_EQ("value1", actual_header_block["key1"]);
+}
+
+TEST_P(SpdySMProxyTest, SendSynStream) {
+ if (GetParam() == SPDY2) {
+ // This test is not for SPDY2.
+ return;
+ }
+ uint32 stream_id = 82;
+ BalsaHeaders headers;
+ SpdyHeaderBlock actual_header_block;
+ headers.AppendHeader("key1", "value1");
+ headers.AppendHeader("Host", "www.example.com");
+ headers.SetRequestFirstlineFromStringPieces("GET", "/path", "HTTP/1.1");
+
+ interface_->SendSynStream(stream_id, headers);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnSynStream(stream_id, 0, _, false, false, _))
+ .WillOnce(SaveArg<5>(&actual_header_block));
+ }
+
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ(1, spdy_framer_->frames_received());
+ ASSERT_EQ(5u, actual_header_block.size());
+ ASSERT_EQ("GET", actual_header_block[":method"]);
+ ASSERT_EQ("HTTP/1.1", actual_header_block[":version"]);
+ ASSERT_EQ("/path", actual_header_block[":path"]);
+ ASSERT_EQ("www.example.com", actual_header_block[":host"]);
+ ASSERT_EQ("value1", actual_header_block["key1"]);
+}
+
+TEST_P(SpdySMProxyTest, SendSynReply_SPDY2) {
+ if (GetParam() != SPDY2) {
+ // This test is for SPDY2.
+ return;
+ }
+ uint32 stream_id = 82;
+ BalsaHeaders headers;
+ SpdyHeaderBlock actual_header_block;
+ headers.AppendHeader("key1", "value1");
+ headers.SetResponseFirstlineFromStringPieces("HTTP/1.1", "200", "OK");
+
+ interface_->SendSynReply(stream_id, headers);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+
+ {
+ InSequence s;
+ if (GetParam() < SPDY4) {
+ EXPECT_CALL(*spdy_framer_visitor_, OnSynReply(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ } else {
+ EXPECT_CALL(*spdy_framer_visitor_, OnHeaders(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ }
+ }
+
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ(1, spdy_framer_->frames_received());
+ ASSERT_EQ(3u, actual_header_block.size());
+ ASSERT_EQ("200 OK", actual_header_block["status"]);
+ ASSERT_EQ("HTTP/1.1", actual_header_block["version"]);
+ ASSERT_EQ("value1", actual_header_block["key1"]);
+}
+
+TEST_P(SpdySMProxyTest, SendSynReply) {
+ if (GetParam() == SPDY2) {
+ // This test is not for SPDY2.
+ return;
+ }
+ uint32 stream_id = 82;
+ BalsaHeaders headers;
+ SpdyHeaderBlock actual_header_block;
+ headers.AppendHeader("key1", "value1");
+ headers.SetResponseFirstlineFromStringPieces("HTTP/1.1", "200", "OK");
+
+ interface_->SendSynReply(stream_id, headers);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+
+ {
+ InSequence s;
+ if (GetParam() < SPDY4) {
+ EXPECT_CALL(*spdy_framer_visitor_, OnSynReply(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ } else {
+ EXPECT_CALL(*spdy_framer_visitor_, OnHeaders(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ }
+ }
+
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ(1, spdy_framer_->frames_received());
+ ASSERT_EQ(3u, actual_header_block.size());
+ ASSERT_EQ("200 OK", actual_header_block[":status"]);
+ ASSERT_EQ("HTTP/1.1", actual_header_block[":version"]);
+ ASSERT_EQ("value1", actual_header_block["key1"]);
+}
+
+TEST_P(SpdySMProxyTest, SendDataFrame) {
+ uint32 stream_id = 133;
+ SpdyDataFlags flags = DATA_FLAG_NONE;
+ const char* actual_data;
+ size_t actual_size;
+
+ interface_->SendDataFrame(stream_id, "hello", 5, flags, true);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+
+ {
+ InSequence s;
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnDataFrameHeader(stream_id, _, false));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, _, _, false))
+ .WillOnce(DoAll(SaveArg<1>(&actual_data), SaveArg<2>(&actual_size)));
+ }
+
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ(1, spdy_framer_->frames_received());
+ ASSERT_EQ("hello", StringPiece(actual_data, actual_size));
+}
+
+TEST_P(SpdySMProxyTest, SendLongDataFrame) {
+ uint32 stream_id = 133;
+ SpdyDataFlags flags = DATA_FLAG_NONE;
+ const char* actual_data;
+ size_t actual_size;
+
+ std::string data = std::string(kSpdySegmentSize, 'a') +
+ std::string(kSpdySegmentSize, 'b') + "c";
+ interface_->SendDataFrame(stream_id, data.data(), data.size(), flags, true);
+
+ {
+ InSequence s;
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnDataFrameHeader(stream_id, _, false));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, _, _, false))
+ .WillOnce(DoAll(SaveArg<1>(&actual_data),
+ SaveArg<2>(&actual_size)));
+ }
+ }
+
+ ASSERT_EQ(3u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ(std::string(kSpdySegmentSize, 'a'),
+ StringPiece(actual_data, actual_size));
+
+ df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ(std::string(kSpdySegmentSize, 'b'),
+ StringPiece(actual_data, actual_size));
+
+ df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+ ASSERT_EQ("c", StringPiece(actual_data, actual_size));
+}
+
+TEST_P(SpdySMProxyTest, SendEOF_SPDY2) {
+ // This test is for SPDY2.
+ if (GetParam() != SPDY2) {
+ return;
+ }
+
+ uint32 stream_id = 82;
+ // SPDY2 data frame
+ char empty_data_frame[] = {'\0', '\0', '\0', '\x52', '\x1', '\0', '\0', '\0'};
+ MemCacheIter mci;
+ mci.stream_id = stream_id;
+
+ {
+ BalsaHeaders headers;
+ std::string filename = "foobar";
+ memory_cache_->InsertFile(&headers, filename, "");
+ mci.file_data = memory_cache_->GetFileData(filename);
+ }
+
+ interface_->AddToOutputOrder(mci);
+ ASSERT_TRUE(HasStream(stream_id));
+ interface_->SendEOF(stream_id);
+ ASSERT_FALSE(HasStream(stream_id));
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ ASSERT_EQ(StringPiece(empty_data_frame, sizeof(empty_data_frame)),
+ StringPiece(df->data, df->size));
+}
+
+TEST_P(SpdySMProxyTest, SendEmptyDataFrame_SPDY2) {
+ // This test is for SPDY2.
+ if (GetParam() != SPDY2) {
+ return;
+ }
+
+ uint32 stream_id = 133;
+ SpdyDataFlags flags = DATA_FLAG_NONE;
+ // SPDY2 data frame
+ char expected[] = {'\0', '\0', '\0', '\x85', '\0', '\0', '\0', '\0'};
+
+ interface_->SendDataFrame(stream_id, "hello", 0, flags, true);
+
+ ASSERT_EQ(1u, connection_->output_list()->size());
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+
+ ASSERT_EQ(StringPiece(expected, sizeof(expected)),
+ StringPiece(df->data, df->size));
+}
+
+TEST_P(SpdySMServerTest, OnSynStream) {
+ BufferedSpdyFramerVisitorInterface* visitor = interface_.get();
+ uint32 stream_id = 82;
+ SpdyHeaderBlock spdy_headers;
+ spdy_headers["url"] = "http://www.example.com/path";
+ spdy_headers["method"] = "GET";
+ spdy_headers["scheme"] = "http";
+ spdy_headers["version"] = "HTTP/1.1";
+
+ {
+ BalsaHeaders headers;
+ memory_cache_->InsertFile(&headers, "GET_/path", "");
+ }
+ visitor->OnSynStream(stream_id, 0, 0, true, true, spdy_headers);
+ ASSERT_TRUE(HasStream(stream_id));
+}
+
+TEST_P(SpdySMServerTest, NewStream) {
+ uint32 stream_id = 13;
+ std::string filename = "foobar";
+
+ {
+ BalsaHeaders headers;
+ memory_cache_->InsertFile(&headers, filename, "");
+ }
+
+ interface_->NewStream(stream_id, 0, filename);
+ ASSERT_TRUE(HasStream(stream_id));
+}
+
+TEST_P(SpdySMServerTest, NewStreamError) {
+ uint32 stream_id = 82;
+ SpdyHeaderBlock actual_header_block;
+ const char* actual_data;
+ size_t actual_size;
+ testing::MockFunction<void(int)> checkpoint; // NOLINT
+
+ interface_->NewStream(stream_id, 0, "nonexistingfile");
+
+ ASSERT_EQ(2u, connection_->output_list()->size());
+
+ {
+ InSequence s;
+ if (GetParam() < SPDY4) {
+ EXPECT_CALL(*spdy_framer_visitor_, OnSynReply(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ } else {
+ EXPECT_CALL(*spdy_framer_visitor_, OnHeaders(stream_id, false, _))
+ .WillOnce(SaveArg<2>(&actual_header_block));
+ }
+ EXPECT_CALL(checkpoint, Call(0));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnDataFrameHeader(stream_id, _, true));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, _, _, false)).Times(1)
+ .WillOnce(DoAll(SaveArg<1>(&actual_data),
+ SaveArg<2>(&actual_size)));
+ EXPECT_CALL(*spdy_framer_visitor_,
+ OnStreamFrameData(stream_id, NULL, 0, true)).Times(1);
+ }
+
+ std::list<DataFrame*>::const_iterator i = connection_->output_list()->begin();
+ DataFrame* df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+ checkpoint.Call(0);
+ df = *i++;
+ spdy_framer_->ProcessInput(df->data, df->size);
+
+ ASSERT_EQ(2, spdy_framer_->frames_received());
+ ASSERT_EQ(2u, actual_header_block.size());
+ ASSERT_EQ("404 Not Found", actual_header_block["status"]);
+ ASSERT_EQ("HTTP/1.1", actual_header_block["version"]);
+ ASSERT_EQ("wtf?", StringPiece(actual_data, actual_size));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/net/tools/flip_server/spdy_ssl.cc b/net/tools/flip_server/spdy_ssl.cc
new file mode 100644
index 0000000..19ea52e
--- /dev/null
+++ b/net/tools/flip_server/spdy_ssl.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 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.
+
+#include "net/tools/flip_server/spdy_ssl.h"
+
+#include "base/logging.h"
+#include "openssl/err.h"
+#include "openssl/ssl.h"
+
+namespace net {
+
+// Each element consists of <the length of the string><string> .
+#define NEXT_PROTO_STRING \
+ "\x08spdy/4a2" \
+ "\x06spdy/3" \
+ "\x06spdy/2" \
+ "\x08http/1.1" \
+ "\x08http/1.0"
+#define SSL_CIPHER_LIST "!aNULL:!ADH:!eNull:!LOW:!EXP:RC4+RSA:MEDIUM:HIGH"
+
+int ssl_set_npn_callback(SSL* s,
+ const unsigned char** data,
+ unsigned int* len,
+ void* arg) {
+ VLOG(1) << "SSL NPN callback: advertising protocols.";
+ *data = (const unsigned char*)NEXT_PROTO_STRING;
+ *len = strlen(NEXT_PROTO_STRING);
+ return SSL_TLSEXT_ERR_OK;
+}
+
+void InitSSL(SSLState* state,
+ std::string ssl_cert_name,
+ std::string ssl_key_name,
+ bool use_npn,
+ int session_expiration_time,
+ bool disable_ssl_compression) {
+ SSL_library_init();
+ PrintSslError();
+
+ SSL_load_error_strings();
+ PrintSslError();
+
+ state->ssl_method = SSLv23_method();
+ state->ssl_ctx = SSL_CTX_new(state->ssl_method);
+ if (!state->ssl_ctx) {
+ PrintSslError();
+ LOG(FATAL) << "Unable to create SSL context";
+ }
+ // Disable SSLv2 support.
+ SSL_CTX_set_options(state->ssl_ctx,
+ SSL_OP_NO_SSLv2 | SSL_OP_CIPHER_SERVER_PREFERENCE);
+ if (SSL_CTX_use_certificate_chain_file(state->ssl_ctx,
+ ssl_cert_name.c_str()) <= 0) {
+ PrintSslError();
+ LOG(FATAL) << "Unable to use cert.pem as SSL cert.";
+ }
+ if (SSL_CTX_use_PrivateKey_file(
+ state->ssl_ctx, ssl_key_name.c_str(), SSL_FILETYPE_PEM) <= 0) {
+ PrintSslError();
+ LOG(FATAL) << "Unable to use key.pem as SSL key.";
+ }
+ if (!SSL_CTX_check_private_key(state->ssl_ctx)) {
+ PrintSslError();
+ LOG(FATAL) << "The cert.pem and key.pem files don't match";
+ }
+ if (use_npn) {
+ SSL_CTX_set_next_protos_advertised_cb(
+ state->ssl_ctx, ssl_set_npn_callback, NULL);
+ }
+ VLOG(1) << "SSL CTX default cipher list: " << SSL_CIPHER_LIST;
+ SSL_CTX_set_cipher_list(state->ssl_ctx, SSL_CIPHER_LIST);
+
+ VLOG(1) << "SSL CTX session expiry: " << session_expiration_time
+ << " seconds";
+ SSL_CTX_set_timeout(state->ssl_ctx, session_expiration_time);
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ VLOG(1) << "SSL CTX: Setting Release Buffers mode.";
+ SSL_CTX_set_mode(state->ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+#endif
+
+#if !defined(OPENSSL_IS_BORINGSSL)
+ // Proper methods to disable compression don't exist until 0.9.9+. For now
+ // we must manipulate the stack of compression methods directly.
+ if (disable_ssl_compression) {
+ STACK_OF(SSL_COMP)* ssl_comp_methods = SSL_COMP_get_compression_methods();
+ int num_methods = sk_SSL_COMP_num(ssl_comp_methods);
+ int i;
+ for (i = 0; i < num_methods; i++) {
+ static_cast<void>(sk_SSL_COMP_delete(ssl_comp_methods, i));
+ }
+ }
+#endif
+}
+
+SSL* CreateSSLContext(SSL_CTX* ssl_ctx) {
+ SSL* ssl = SSL_new(ssl_ctx);
+ SSL_set_accept_state(ssl);
+ PrintSslError();
+ return ssl;
+}
+
+void PrintSslError() {
+ char buf[128]; // this buffer must be at least 120 chars long.
+ int error_num = ERR_get_error();
+ while (error_num != 0) {
+ ERR_error_string_n(error_num, buf, sizeof(buf));
+ LOG(ERROR) << buf;
+ error_num = ERR_get_error();
+ }
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/spdy_ssl.h b/net/tools/flip_server/spdy_ssl.h
new file mode 100644
index 0000000..8b55733
--- /dev/null
+++ b/net/tools/flip_server/spdy_ssl.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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_TOOLS_FLIP_SERVER_SPDY_SSL_H_
+#define NET_TOOLS_FLIP_SERVER_SPDY_SSL_H_
+
+#include <string>
+
+#include "openssl/ssl.h"
+
+namespace net {
+
+struct SSLState {
+ const SSL_METHOD* ssl_method;
+ SSL_CTX* ssl_ctx;
+};
+
+void InitSSL(SSLState* state,
+ std::string ssl_cert_name,
+ std::string ssl_key_name,
+ bool use_npn,
+ int session_expiration_time,
+ bool disable_ssl_compression);
+SSL* CreateSSLContext(SSL_CTX* ssl_ctx);
+void PrintSslError();
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SPDY_SSL_H_
diff --git a/net/tools/flip_server/spdy_util.cc b/net/tools/flip_server/spdy_util.cc
new file mode 100644
index 0000000..311bdb9
--- /dev/null
+++ b/net/tools/flip_server/spdy_util.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/spdy_util.h"
+
+#include <string>
+
+#include "net/tools/dump_cache/url_to_filename_encoder.h"
+
+namespace net {
+
+bool g_need_to_encode_url = false;
+
+// Encode the URL.
+std::string EncodeURL(std::string uri, std::string host, std::string method) {
+ if (!g_need_to_encode_url) {
+ // TODO(mbelshe): if uri is fully qualified, need to strip protocol/host.
+ return std::string(method + "_" + uri);
+ }
+
+ std::string filename;
+ if (uri[0] == '/') {
+ // uri is not fully qualified.
+ filename = UrlToFilenameEncoder::Encode(
+ "http://" + host + uri, method + "_/", false);
+ } else {
+ filename = UrlToFilenameEncoder::Encode(uri, method + "_/", false);
+ }
+ return filename;
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/spdy_util.h b/net/tools/flip_server/spdy_util.h
new file mode 100644
index 0000000..6f00200
--- /dev/null
+++ b/net/tools/flip_server/spdy_util.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2009 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_TOOLS_FLIP_SERVER_SPDY_UTIL_H_
+#define NET_TOOLS_FLIP_SERVER_SPDY_UTIL_H_
+
+#include <string>
+
+namespace net {
+
+// Flag indicating if we need to encode urls into filenames (legacy).
+extern bool g_need_to_encode_url;
+
+// Encode the URL.
+std::string EncodeURL(std::string uri, std::string host, std::string method);
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_SPDY_UTIL_H_
diff --git a/net/tools/flip_server/streamer_interface.cc b/net/tools/flip_server/streamer_interface.cc
new file mode 100644
index 0000000..25a4964
--- /dev/null
+++ b/net/tools/flip_server/streamer_interface.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2009 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.
+
+#include "net/tools/flip_server/streamer_interface.h"
+
+#include <string>
+
+#include "net/tools/balsa/balsa_frame.h"
+#include "net/tools/flip_server/constants.h"
+#include "net/tools/flip_server/flip_config.h"
+#include "net/tools/flip_server/sm_connection.h"
+
+namespace net {
+
+std::string StreamerSM::forward_ip_header_;
+
+StreamerSM::StreamerSM(SMConnection* connection,
+ SMInterface* sm_other_interface,
+ EpollServer* epoll_server,
+ FlipAcceptor* acceptor)
+ : connection_(connection),
+ sm_other_interface_(sm_other_interface),
+ epoll_server_(epoll_server),
+ acceptor_(acceptor),
+ is_request_(false),
+ http_framer_(new BalsaFrame) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "Creating StreamerSM object";
+ http_framer_->set_balsa_visitor(this);
+ http_framer_->set_balsa_headers(&headers_);
+ http_framer_->set_is_request(false);
+}
+
+StreamerSM::~StreamerSM() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Destroying StreamerSM object";
+ Reset();
+ delete http_framer_;
+}
+
+void StreamerSM::set_is_request() {
+ is_request_ = true;
+ http_framer_->set_is_request(true);
+}
+
+void StreamerSM::InitSMInterface(SMInterface* sm_other_interface,
+ int32 server_idx) {
+ sm_other_interface_ = sm_other_interface;
+}
+
+void StreamerSM::InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StreamerSM: Initializing server "
+ << "connection.";
+ connection_->InitSMConnection(connection_pool,
+ sm_interface,
+ epoll_server,
+ fd,
+ server_ip,
+ server_port,
+ remote_ip,
+ use_ssl);
+}
+
+size_t StreamerSM::ProcessReadInput(const char* data, size_t len) {
+ // For now we only want to parse http requests. Just stream responses
+ if (is_request_) {
+ return http_framer_->ProcessInput(data, len);
+ } else {
+ return sm_other_interface_->ProcessWriteInput(data, len);
+ }
+}
+
+size_t StreamerSM::ProcessWriteInput(const char* data, size_t len) {
+ char* dataPtr = new char[len];
+ memcpy(dataPtr, data, len);
+ DataFrame* df = new DataFrame;
+ df->data = (const char*)dataPtr;
+ df->size = len;
+ df->delete_when_done = true;
+ connection_->EnqueueDataFrame(df);
+ return len;
+}
+
+bool StreamerSM::Error() const { return false; }
+
+const char* StreamerSM::ErrorAsString() const { return "(none)"; }
+
+bool StreamerSM::MessageFullyRead() const {
+ if (is_request_) {
+ return http_framer_->MessageFullyRead();
+ } else {
+ return false;
+ }
+}
+
+void StreamerSM::Reset() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "StreamerSM: Reset";
+ connection_->Cleanup("Server Reset");
+ http_framer_->Reset();
+}
+
+void StreamerSM::ResetForNewConnection() {
+ http_framer_->Reset();
+ sm_other_interface_->Reset();
+}
+
+void StreamerSM::Cleanup() {
+ if (is_request_)
+ http_framer_->Reset();
+}
+
+int StreamerSM::PostAcceptHook() {
+ if (!sm_other_interface_) {
+ SMConnection* server_connection = SMConnection::NewSMConnection(
+ epoll_server_, NULL, NULL, acceptor_, "server_conn: ");
+ if (server_connection == NULL) {
+ LOG(ERROR) << "StreamerSM: Could not create server conenction.";
+ return 0;
+ }
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StreamerSM: Creating new server "
+ << "connection.";
+ sm_other_interface_ =
+ new StreamerSM(server_connection, this, epoll_server_, acceptor_);
+ sm_other_interface_->InitSMInterface(this, 0);
+ }
+ // The Streamer interface is used to stream HTTPS connections, so we
+ // will always use the https_server_ip/port here.
+ sm_other_interface_->InitSMConnection(NULL,
+ sm_other_interface_,
+ epoll_server_,
+ -1,
+ acceptor_->https_server_ip_,
+ acceptor_->https_server_port_,
+ std::string(),
+ false);
+
+ return 1;
+}
+
+size_t StreamerSM::SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) {
+ return 0;
+}
+
+size_t StreamerSM::SendSynReply(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+}
+
+void StreamerSM::ProcessBodyInput(const char* input, size_t size) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT
+ << "StreamerHttpSM: Process Body Input Data: "
+ << "size " << size;
+ sm_other_interface_->ProcessWriteInput(input, size);
+}
+
+void StreamerSM::MessageDone() {
+ if (acceptor_->flip_handler_type_ == FLIP_HANDLER_PROXY) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StreamerHttpSM: MessageDone.";
+ // TODO(kelindsay): anything need to be done ehre?
+ } else {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "StraemerHttpSM: MessageDone.";
+ }
+}
+
+void StreamerSM::ProcessHeaders(const BalsaHeaders& headers) {
+ VLOG(2) << ACCEPTOR_CLIENT_IDENT << "HttpStreamerSM: Process Headers";
+ BalsaHeaders mod_headers;
+ mod_headers.CopyFrom(headers);
+ if (forward_ip_header_.length()) {
+ LOG(INFO) << "Adding forward header: " << forward_ip_header_;
+ mod_headers.ReplaceOrAppendHeader(forward_ip_header_,
+ connection_->client_ip());
+ } else {
+ LOG(INFO) << "NOT adding forward header.";
+ }
+ SimpleBuffer sb;
+ char* buffer;
+ int size;
+ mod_headers.WriteHeaderAndEndingToBuffer(&sb);
+ sb.GetReadablePtr(&buffer, &size);
+ sm_other_interface_->ProcessWriteInput(buffer, size);
+}
+
+void StreamerSM::HandleHeaderError(BalsaFrame* framer) { HandleError(); }
+
+void StreamerSM::HandleChunkingError(BalsaFrame* framer) { HandleError(); }
+
+void StreamerSM::HandleBodyError(BalsaFrame* framer) { HandleError(); }
+
+void StreamerSM::HandleError() {
+ VLOG(1) << ACCEPTOR_CLIENT_IDENT << "Error detected";
+}
+
+} // namespace net
diff --git a/net/tools/flip_server/streamer_interface.h b/net/tools/flip_server/streamer_interface.h
new file mode 100644
index 0000000..0114491
--- /dev/null
+++ b/net/tools/flip_server/streamer_interface.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2011 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_TOOLS_FLIP_SERVER_STREAMER_INTERFACE_H_
+#define NET_TOOLS_FLIP_SERVER_STREAMER_INTERFACE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "net/tools/balsa/balsa_headers.h"
+#include "net/tools/balsa/balsa_visitor_interface.h"
+#include "net/tools/flip_server/sm_interface.h"
+
+namespace net {
+
+class BalsaFrame;
+class FlipAcceptor;
+class MemCacheIter;
+class SMConnection;
+class EpollServer;
+
+class StreamerSM : public BalsaVisitorInterface, public SMInterface {
+ public:
+ StreamerSM(SMConnection* connection,
+ SMInterface* sm_other_interface,
+ EpollServer* epoll_server,
+ FlipAcceptor* acceptor);
+ virtual ~StreamerSM();
+
+ void AddToOutputOrder(const MemCacheIter& mci) {}
+
+ virtual void InitSMInterface(SMInterface* sm_other_interface,
+ int32 server_idx) OVERRIDE;
+ virtual void InitSMConnection(SMConnectionPoolInterface* connection_pool,
+ SMInterface* sm_interface,
+ EpollServer* epoll_server,
+ int fd,
+ std::string server_ip,
+ std::string server_port,
+ std::string remote_ip,
+ bool use_ssl) OVERRIDE;
+
+ virtual size_t ProcessReadInput(const char* data, size_t len) OVERRIDE;
+ virtual size_t ProcessWriteInput(const char* data, size_t len) OVERRIDE;
+ virtual bool MessageFullyRead() const OVERRIDE;
+ virtual void SetStreamID(uint32 stream_id) OVERRIDE {}
+ virtual bool Error() const OVERRIDE;
+ virtual const char* ErrorAsString() const OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual void ResetForNewInterface(int32 server_idx) OVERRIDE {}
+ virtual void ResetForNewConnection() OVERRIDE;
+ virtual void Cleanup() OVERRIDE;
+ virtual int PostAcceptHook() OVERRIDE;
+ virtual void NewStream(uint32 stream_id,
+ uint32 priority,
+ const std::string& filename) OVERRIDE {}
+ virtual void SendEOF(uint32 stream_id) OVERRIDE {}
+ virtual void SendErrorNotFound(uint32 stream_id) OVERRIDE {}
+ virtual void SendOKResponse(uint32 stream_id, std::string output) {}
+ virtual size_t SendSynStream(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual size_t SendSynReply(uint32 stream_id,
+ const BalsaHeaders& headers) OVERRIDE;
+ virtual void SendDataFrame(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) OVERRIDE {}
+ virtual void set_is_request() OVERRIDE;
+ static std::string forward_ip_header() { return forward_ip_header_; }
+ static void set_forward_ip_header(std::string value) {
+ forward_ip_header_ = value;
+ }
+
+ private:
+ void SendEOFImpl(uint32 stream_id) {}
+ void SendErrorNotFoundImpl(uint32 stream_id) {}
+ void SendOKResponseImpl(uint32 stream_id, std::string* output) {}
+ size_t SendSynReplyImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+ }
+ size_t SendSynStreamImpl(uint32 stream_id, const BalsaHeaders& headers) {
+ return 0;
+ }
+ void SendDataFrameImpl(uint32 stream_id,
+ const char* data,
+ int64 len,
+ uint32 flags,
+ bool compress) {}
+ virtual void GetOutput() OVERRIDE {}
+
+ virtual void ProcessBodyInput(const char* input, size_t size) OVERRIDE;
+ virtual void MessageDone() OVERRIDE;
+ virtual void ProcessHeaders(const BalsaHeaders& headers) OVERRIDE;
+ virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessHeaderInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessTrailerInput(const char* input, size_t size) OVERRIDE {}
+ virtual void ProcessRequestFirstLine(const char* line_input,
+ size_t line_length,
+ const char* method_input,
+ size_t method_length,
+ const char* request_uri_input,
+ size_t request_uri_length,
+ const char* version_input,
+ size_t version_length) OVERRIDE {}
+ virtual void ProcessResponseFirstLine(const char* line_input,
+ size_t line_length,
+ const char* version_input,
+ size_t version_length,
+ const char* status_input,
+ size_t status_length,
+ const char* reason_input,
+ size_t reason_length) OVERRIDE {}
+ virtual void ProcessChunkLength(size_t chunk_length) OVERRIDE {}
+ virtual void ProcessChunkExtensions(const char* input, size_t size) OVERRIDE {
+ }
+ virtual void HeaderDone() OVERRIDE {}
+ virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE {}
+ virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE;
+ virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE;
+ void HandleError();
+
+ SMConnection* connection_;
+ SMInterface* sm_other_interface_;
+ EpollServer* epoll_server_;
+ FlipAcceptor* acceptor_;
+ bool is_request_;
+ BalsaFrame* http_framer_;
+ BalsaHeaders headers_;
+ static std::string forward_ip_header_;
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_FLIP_SERVER_STREAMER_INTERFACE_H_