diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/examples/peerconnection/server | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/examples/peerconnection/server')
8 files changed, 1409 insertions, 0 deletions
diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc new file mode 100644 index 0000000000..855ebd8c0c --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.cc @@ -0,0 +1,299 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "examples/peerconnection/server/data_socket.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if defined(WEBRTC_POSIX) +#include <unistd.h> +#endif + +#include "examples/peerconnection/server/utils.h" +#include "rtc_base/checks.h" + +static const char kHeaderTerminator[] = "\r\n\r\n"; +static const int kHeaderTerminatorLength = sizeof(kHeaderTerminator) - 1; + +// static +const char DataSocket::kCrossOriginAllowHeaders[] = + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Credentials: true\r\n" + "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" + "Access-Control-Allow-Headers: Content-Type, " + "Content-Length, Connection, Cache-Control\r\n" + "Access-Control-Expose-Headers: Content-Length\r\n"; + +#if defined(WIN32) +class WinsockInitializer { + static WinsockInitializer singleton; + + WinsockInitializer() { + WSADATA data; + WSAStartup(MAKEWORD(1, 0), &data); + } + + public: + ~WinsockInitializer() { WSACleanup(); } +}; +WinsockInitializer WinsockInitializer::singleton; +#endif + +// +// SocketBase +// + +bool SocketBase::Create() { + RTC_DCHECK(!valid()); + socket_ = ::socket(AF_INET, SOCK_STREAM, 0); + return valid(); +} + +void SocketBase::Close() { + if (socket_ != INVALID_SOCKET) { + closesocket(socket_); + socket_ = INVALID_SOCKET; + } +} + +// +// DataSocket +// + +std::string DataSocket::request_arguments() const { + size_t args = request_path_.find('?'); + if (args != std::string::npos) + return request_path_.substr(args + 1); + return ""; +} + +bool DataSocket::PathEquals(const char* path) const { + RTC_DCHECK(path); + size_t args = request_path_.find('?'); + if (args != std::string::npos) + return request_path_.substr(0, args).compare(path) == 0; + return request_path_.compare(path) == 0; +} + +bool DataSocket::OnDataAvailable(bool* close_socket) { + RTC_DCHECK(valid()); + char buffer[0xfff] = {0}; + int bytes = recv(socket_, buffer, sizeof(buffer), 0); + if (bytes == SOCKET_ERROR || bytes == 0) { + *close_socket = true; + return false; + } + + *close_socket = false; + + bool ret = true; + if (headers_received()) { + if (method_ != POST) { + // unexpectedly received data. + ret = false; + } else { + data_.append(buffer, bytes); + } + } else { + request_headers_.append(buffer, bytes); + size_t found = request_headers_.find(kHeaderTerminator); + if (found != std::string::npos) { + data_ = request_headers_.substr(found + kHeaderTerminatorLength); + request_headers_.resize(found + kHeaderTerminatorLength); + ret = ParseHeaders(); + } + } + return ret; +} + +bool DataSocket::Send(const std::string& data) const { + return send(socket_, data.data(), static_cast<int>(data.length()), 0) != + SOCKET_ERROR; +} + +bool DataSocket::Send(const std::string& status, + bool connection_close, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data) const { + RTC_DCHECK(valid()); + RTC_DCHECK(!status.empty()); + std::string buffer("HTTP/1.1 " + status + "\r\n"); + + buffer += + "Server: PeerConnectionTestServer/0.1\r\n" + "Cache-Control: no-cache\r\n"; + + if (connection_close) + buffer += "Connection: close\r\n"; + + if (!content_type.empty()) + buffer += "Content-Type: " + content_type + "\r\n"; + + buffer += + "Content-Length: " + int2str(static_cast<int>(data.size())) + "\r\n"; + + if (!extra_headers.empty()) { + buffer += extra_headers; + // Extra headers are assumed to have a separator per header. + } + + buffer += kCrossOriginAllowHeaders; + + buffer += "\r\n"; + buffer += data; + + return Send(buffer); +} + +void DataSocket::Clear() { + method_ = INVALID; + content_length_ = 0; + content_type_.clear(); + request_path_.clear(); + request_headers_.clear(); + data_.clear(); +} + +bool DataSocket::ParseHeaders() { + RTC_DCHECK(!request_headers_.empty()); + RTC_DCHECK_EQ(method_, INVALID); + size_t i = request_headers_.find("\r\n"); + if (i == std::string::npos) + return false; + + if (!ParseMethodAndPath(request_headers_.data(), i)) + return false; + + RTC_DCHECK_NE(method_, INVALID); + RTC_DCHECK(!request_path_.empty()); + + if (method_ == POST) { + const char* headers = request_headers_.data() + i + 2; + size_t len = request_headers_.length() - i - 2; + if (!ParseContentLengthAndType(headers, len)) + return false; + } + + return true; +} + +bool DataSocket::ParseMethodAndPath(const char* begin, size_t len) { + struct { + const char* method_name; + size_t method_name_len; + RequestMethod id; + } supported_methods[] = { + {"GET", 3, GET}, + {"POST", 4, POST}, + {"OPTIONS", 7, OPTIONS}, + }; + + const char* path = NULL; + for (size_t i = 0; i < ARRAYSIZE(supported_methods); ++i) { + if (len > supported_methods[i].method_name_len && + isspace(begin[supported_methods[i].method_name_len]) && + strncmp(begin, supported_methods[i].method_name, + supported_methods[i].method_name_len) == 0) { + method_ = supported_methods[i].id; + path = begin + supported_methods[i].method_name_len; + break; + } + } + + const char* end = begin + len; + if (!path || path >= end) + return false; + + ++path; + begin = path; + while (!isspace(*path) && path < end) + ++path; + + request_path_.assign(begin, path - begin); + + return true; +} + +bool DataSocket::ParseContentLengthAndType(const char* headers, size_t length) { + RTC_DCHECK_EQ(content_length_, 0); + RTC_DCHECK(content_type_.empty()); + + const char* end = headers + length; + while (headers && headers < end) { + if (!isspace(headers[0])) { + static const char kContentLength[] = "Content-Length:"; + static const char kContentType[] = "Content-Type:"; + if ((headers + ARRAYSIZE(kContentLength)) < end && + strncmp(headers, kContentLength, ARRAYSIZE(kContentLength) - 1) == + 0) { + headers += ARRAYSIZE(kContentLength) - 1; + while (headers[0] == ' ') + ++headers; + content_length_ = atoi(headers); + } else if ((headers + ARRAYSIZE(kContentType)) < end && + strncmp(headers, kContentType, ARRAYSIZE(kContentType) - 1) == + 0) { + headers += ARRAYSIZE(kContentType) - 1; + while (headers[0] == ' ') + ++headers; + const char* type_end = strstr(headers, "\r\n"); + if (type_end == NULL) + type_end = end; + content_type_.assign(headers, type_end); + } + } else { + ++headers; + } + headers = strstr(headers, "\r\n"); + if (headers) + headers += 2; + } + + return !content_type_.empty() && content_length_ != 0; +} + +// +// ListeningSocket +// + +bool ListeningSocket::Listen(unsigned short port) { + RTC_DCHECK(valid()); + int enabled = 1; + if (setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast<const char*>(&enabled), + sizeof(enabled)) != 0) { + printf("setsockopt failed\n"); + return false; + } + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + if (bind(socket_, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)) == + SOCKET_ERROR) { + printf("bind failed\n"); + return false; + } + return listen(socket_, 5) != SOCKET_ERROR; +} + +DataSocket* ListeningSocket::Accept() const { + RTC_DCHECK(valid()); + struct sockaddr_in addr = {0}; + socklen_t size = sizeof(addr); + NativeSocket client = + accept(socket_, reinterpret_cast<sockaddr*>(&addr), &size); + if (client == INVALID_SOCKET) + return NULL; + + return new DataSocket(client); +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/data_socket.h b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h new file mode 100644 index 0000000000..57ad5b9aee --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/data_socket.h @@ -0,0 +1,152 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ + +#ifdef WIN32 +#include <winsock2.h> +typedef int socklen_t; +typedef SOCKET NativeSocket; +#else +#include <netinet/in.h> +#include <sys/select.h> +#include <sys/socket.h> +#define closesocket close +typedef int NativeSocket; + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR (-1) +#endif + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET static_cast<NativeSocket>(-1) +#endif +#endif + +#include <string> + +class SocketBase { + public: + SocketBase() : socket_(INVALID_SOCKET) {} + explicit SocketBase(NativeSocket socket) : socket_(socket) {} + SocketBase(SocketBase& other) = delete; + SocketBase& operator=(const SocketBase& other) = delete; + ~SocketBase() { Close(); } + + NativeSocket socket() const { return socket_; } + bool valid() const { return socket_ != INVALID_SOCKET; } + + bool Create(); + void Close(); + + protected: + NativeSocket socket_; +}; + +// Represents an HTTP server socket. +class DataSocket : public SocketBase { + public: + enum RequestMethod { + INVALID, + GET, + POST, + OPTIONS, + }; + + explicit DataSocket(NativeSocket socket) + : SocketBase(socket), method_(INVALID), content_length_(0) {} + + ~DataSocket() {} + + static const char kCrossOriginAllowHeaders[]; + + bool headers_received() const { return method_ != INVALID; } + + RequestMethod method() const { return method_; } + + const std::string& request_path() const { return request_path_; } + std::string request_arguments() const; + + const std::string& data() const { return data_; } + + const std::string& content_type() const { return content_type_; } + + size_t content_length() const { return content_length_; } + + bool request_received() const { + return headers_received() && (method_ != POST || data_received()); + } + + bool data_received() const { + return method_ != POST || data_.length() >= content_length_; + } + + // Checks if the request path (minus arguments) matches a given path. + bool PathEquals(const char* path) const; + + // Called when we have received some data from clients. + // Returns false if an error occurred. + bool OnDataAvailable(bool* close_socket); + + // Send a raw buffer of bytes. + bool Send(const std::string& data) const; + + // Send an HTTP response. The `status` should start with a valid HTTP + // response code, followed by a string. E.g. "200 OK". + // If `connection_close` is set to true, an extra "Connection: close" HTTP + // header will be included. `content_type` is the mime content type, not + // including the "Content-Type: " string. + // `extra_headers` should be either empty or a list of headers where each + // header terminates with "\r\n". + // `data` is the body of the message. It's length will be specified via + // a "Content-Length" header. + bool Send(const std::string& status, + bool connection_close, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data) const; + + // Clears all held state and prepares the socket for receiving a new request. + void Clear(); + + protected: + // A fairly relaxed HTTP header parser. Parses the method, path and + // content length (POST only) of a request. + // Returns true if a valid request was received and no errors occurred. + bool ParseHeaders(); + + // Figures out whether the request is a GET or POST and what path is + // being requested. + bool ParseMethodAndPath(const char* begin, size_t len); + + // Determines the length of the body and it's mime type. + bool ParseContentLengthAndType(const char* headers, size_t length); + + protected: + RequestMethod method_; + size_t content_length_; + std::string content_type_; + std::string request_path_; + std::string request_headers_; + std::string data_; +}; + +// The server socket. Accepts connections and generates DataSocket instances +// for each new connection. +class ListeningSocket : public SocketBase { + public: + ListeningSocket() {} + + bool Listen(unsigned short port); + DataSocket* Accept() const; +}; + +#endif // EXAMPLES_PEERCONNECTION_SERVER_DATA_SOCKET_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/server/main.cc b/third_party/libwebrtc/examples/peerconnection/server/main.cc new file mode 100644 index 0000000000..50b8c23401 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/main.cc @@ -0,0 +1,193 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <stdio.h> +#include <stdlib.h> +#if defined(WEBRTC_POSIX) +#include <sys/select.h> +#endif +#include <time.h> + +#include <string> +#include <vector> + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/flags/usage.h" +#include "examples/peerconnection/server/data_socket.h" +#include "examples/peerconnection/server/peer_channel.h" +#include "rtc_base/checks.h" +#include "system_wrappers/include/field_trial.h" +#include "test/field_trial.h" + +ABSL_FLAG( + std::string, + force_fieldtrials, + "", + "Field trials control experimental features. This flag specifies the field " + "trials in effect. E.g. running with " + "--force_fieldtrials=WebRTC-FooFeature/Enabled/ " + "will assign the group Enabled to field trial WebRTC-FooFeature. Multiple " + "trials are separated by \"/\""); +ABSL_FLAG(int, port, 8888, "default: 8888"); + +static const size_t kMaxConnections = (FD_SETSIZE - 2); + +void HandleBrowserRequest(DataSocket* ds, bool* quit) { + RTC_DCHECK(ds && ds->valid()); + RTC_DCHECK(quit); + + const std::string& path = ds->request_path(); + + *quit = (path.compare("/quit") == 0); + + if (*quit) { + ds->Send("200 OK", true, "text/html", "", + "<html><body>Quitting...</body></html>"); + } else if (ds->method() == DataSocket::OPTIONS) { + // We'll get this when a browsers do cross-resource-sharing requests. + // The headers to allow cross-origin script support will be set inside + // Send. + ds->Send("200 OK", true, "", "", ""); + } else { + // Here we could write some useful output back to the browser depending on + // the path. + printf("Received an invalid request: %s\n", ds->request_path().c_str()); + ds->Send("500 Sorry", true, "text/html", "", + "<html><body>Sorry, not yet implemented</body></html>"); + } +} + +int main(int argc, char* argv[]) { + absl::SetProgramUsageMessage( + "Example usage: ./peerconnection_server --port=8888\n"); + absl::ParseCommandLine(argc, argv); + + // InitFieldTrialsFromString stores the char*, so the char array must outlive + // the application. + const std::string force_field_trials = absl::GetFlag(FLAGS_force_fieldtrials); + webrtc::field_trial::InitFieldTrialsFromString(force_field_trials.c_str()); + + int port = absl::GetFlag(FLAGS_port); + + // Abort if the user specifies a port that is outside the allowed + // range [1, 65535]. + if ((port < 1) || (port > 65535)) { + printf("Error: %i is not a valid port.\n", port); + return -1; + } + + ListeningSocket listener; + if (!listener.Create()) { + printf("Failed to create server socket\n"); + return -1; + } else if (!listener.Listen(port)) { + printf("Failed to listen on server socket\n"); + return -1; + } + + printf("Server listening on port %i\n", port); + + PeerChannel clients; + typedef std::vector<DataSocket*> SocketArray; + SocketArray sockets; + bool quit = false; + while (!quit) { + fd_set socket_set; + FD_ZERO(&socket_set); + if (listener.valid()) + FD_SET(listener.socket(), &socket_set); + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) + FD_SET((*i)->socket(), &socket_set); + + struct timeval timeout = {10, 0}; + if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) { + printf("select failed\n"); + break; + } + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) { + DataSocket* s = *i; + bool socket_done = true; + if (FD_ISSET(s->socket(), &socket_set)) { + if (s->OnDataAvailable(&socket_done) && s->request_received()) { + ChannelMember* member = clients.Lookup(s); + if (member || PeerChannel::IsPeerConnection(s)) { + if (!member) { + if (s->PathEquals("/sign_in")) { + clients.AddMember(s); + } else { + printf("No member found for: %s\n", s->request_path().c_str()); + s->Send("500 Error", true, "text/plain", "", + "Peer most likely gone."); + } + } else if (member->is_wait_request(s)) { + // no need to do anything. + socket_done = false; + } else { + ChannelMember* target = clients.IsTargetedRequest(s); + if (target) { + member->ForwardRequestToPeer(s, target); + } else if (s->PathEquals("/sign_out")) { + s->Send("200 OK", true, "text/plain", "", ""); + } else { + printf("Couldn't find target for request: %s\n", + s->request_path().c_str()); + s->Send("500 Error", true, "text/plain", "", + "Peer most likely gone."); + } + } + } else { + HandleBrowserRequest(s, &quit); + if (quit) { + printf("Quitting...\n"); + FD_CLR(listener.socket(), &socket_set); + listener.Close(); + clients.CloseAll(); + } + } + } + } else { + socket_done = false; + } + + if (socket_done) { + printf("Disconnecting socket\n"); + clients.OnClosing(s); + RTC_DCHECK(s->valid()); // Close must not have been called yet. + FD_CLR(s->socket(), &socket_set); + delete (*i); + i = sockets.erase(i); + if (i == sockets.end()) + break; + } + } + + clients.CheckForTimeout(); + + if (FD_ISSET(listener.socket(), &socket_set)) { + DataSocket* s = listener.Accept(); + if (sockets.size() >= kMaxConnections) { + delete s; // sorry, that's all we can take. + printf("Connection limit reached\n"); + } else { + sockets.push_back(s); + printf("New connection...\n"); + } + } + } + + for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) + delete (*i); + sockets.clear(); + + return 0; +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc new file mode 100644 index 0000000000..f53820cc60 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.cc @@ -0,0 +1,360 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "examples/peerconnection/server/peer_channel.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <algorithm> + +#include "examples/peerconnection/server/data_socket.h" +#include "examples/peerconnection/server/utils.h" +#include "rtc_base/checks.h" + +// Set to the peer id of the originator when messages are being +// exchanged between peers, but set to the id of the receiving peer +// itself when notifications are sent from the server about the state +// of other peers. +// +// WORKAROUND: Since support for CORS varies greatly from one browser to the +// next, we don't use a custom name for our peer-id header (originally it was +// "X-Peer-Id: "). Instead, we use a "simple header", "Pragma" which should +// always be exposed to CORS requests. There is a special CORS header devoted +// to exposing proprietary headers (Access-Control-Expose-Headers), however +// at this point it is not working correctly in some popular browsers. +static const char kPeerIdHeader[] = "Pragma: "; + +static const char* kRequestPaths[] = { + "/wait", + "/sign_out", + "/message", +}; + +enum RequestPathIndex { + kWait, + kSignOut, + kMessage, +}; + +const size_t kMaxNameLength = 512; + +// +// ChannelMember +// + +int ChannelMember::s_member_id_ = 0; + +ChannelMember::ChannelMember(DataSocket* socket) + : waiting_socket_(NULL), + id_(++s_member_id_), + connected_(true), + timestamp_(time(NULL)) { + RTC_DCHECK(socket); + RTC_DCHECK_EQ(socket->method(), DataSocket::GET); + RTC_DCHECK(socket->PathEquals("/sign_in")); + name_ = socket->request_arguments(); + if (name_.empty()) + name_ = "peer_" + int2str(id_); + else if (name_.length() > kMaxNameLength) + name_.resize(kMaxNameLength); + + std::replace(name_.begin(), name_.end(), ',', '_'); +} + +ChannelMember::~ChannelMember() {} + +bool ChannelMember::is_wait_request(DataSocket* ds) const { + return ds && ds->PathEquals(kRequestPaths[kWait]); +} + +bool ChannelMember::TimedOut() { + return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30; +} + +std::string ChannelMember::GetPeerIdHeader() const { + std::string ret(kPeerIdHeader + int2str(id_) + "\r\n"); + return ret; +} + +bool ChannelMember::NotifyOfOtherMember(const ChannelMember& other) { + RTC_DCHECK_NE(&other, this); + QueueResponse("200 OK", "text/plain", GetPeerIdHeader(), other.GetEntry()); + return true; +} + +// Returns a string in the form "name,id,connected\n". +std::string ChannelMember::GetEntry() const { + RTC_DCHECK(name_.length() <= kMaxNameLength); + + // name, 11-digit int, 1-digit bool, newline, null + char entry[kMaxNameLength + 15]; + snprintf(entry, sizeof(entry), "%s,%d,%d\n", + name_.substr(0, kMaxNameLength).c_str(), id_, connected_); + return entry; +} + +void ChannelMember::ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer) { + RTC_DCHECK(peer); + RTC_DCHECK(ds); + + std::string extra_headers(GetPeerIdHeader()); + + if (peer == this) { + ds->Send("200 OK", true, ds->content_type(), extra_headers, ds->data()); + } else { + printf("Client %s sending to %s\n", name_.c_str(), peer->name().c_str()); + peer->QueueResponse("200 OK", ds->content_type(), extra_headers, + ds->data()); + ds->Send("200 OK", true, "text/plain", "", ""); + } +} + +void ChannelMember::OnClosing(DataSocket* ds) { + if (ds == waiting_socket_) { + waiting_socket_ = NULL; + timestamp_ = time(NULL); + } +} + +void ChannelMember::QueueResponse(const std::string& status, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data) { + if (waiting_socket_) { + RTC_DCHECK(queue_.empty()); + RTC_DCHECK_EQ(waiting_socket_->method(), DataSocket::GET); + bool ok = + waiting_socket_->Send(status, true, content_type, extra_headers, data); + if (!ok) { + printf("Failed to deliver data to waiting socket\n"); + } + waiting_socket_ = NULL; + timestamp_ = time(NULL); + } else { + QueuedResponse qr; + qr.status = status; + qr.content_type = content_type; + qr.extra_headers = extra_headers; + qr.data = data; + queue_.push(qr); + } +} + +void ChannelMember::SetWaitingSocket(DataSocket* ds) { + RTC_DCHECK_EQ(ds->method(), DataSocket::GET); + if (ds && !queue_.empty()) { + RTC_DCHECK(!waiting_socket_); + const QueuedResponse& response = queue_.front(); + ds->Send(response.status, true, response.content_type, + response.extra_headers, response.data); + queue_.pop(); + } else { + waiting_socket_ = ds; + } +} + +// +// PeerChannel +// + +// static +bool PeerChannel::IsPeerConnection(const DataSocket* ds) { + RTC_DCHECK(ds); + return (ds->method() == DataSocket::POST && ds->content_length() > 0) || + (ds->method() == DataSocket::GET && ds->PathEquals("/sign_in")); +} + +ChannelMember* PeerChannel::Lookup(DataSocket* ds) const { + RTC_DCHECK(ds); + + if (ds->method() != DataSocket::GET && ds->method() != DataSocket::POST) + return NULL; + + size_t i = 0; + for (; i < ARRAYSIZE(kRequestPaths); ++i) { + if (ds->PathEquals(kRequestPaths[i])) + break; + } + + if (i == ARRAYSIZE(kRequestPaths)) + return NULL; + + std::string args(ds->request_arguments()); + static const char kPeerId[] = "peer_id="; + size_t found = args.find(kPeerId); + if (found == std::string::npos) + return NULL; + + int id = atoi(&args[found + ARRAYSIZE(kPeerId) - 1]); + Members::const_iterator iter = members_.begin(); + for (; iter != members_.end(); ++iter) { + if (id == (*iter)->id()) { + if (i == kWait) + (*iter)->SetWaitingSocket(ds); + if (i == kSignOut) + (*iter)->set_disconnected(); + return *iter; + } + } + + return NULL; +} + +ChannelMember* PeerChannel::IsTargetedRequest(const DataSocket* ds) const { + RTC_DCHECK(ds); + // Regardless of GET or POST, we look for the peer_id parameter + // only in the request_path. + const std::string& path = ds->request_path(); + size_t args = path.find('?'); + if (args == std::string::npos) + return NULL; + size_t found; + const char kTargetPeerIdParam[] = "to="; + do { + found = path.find(kTargetPeerIdParam, args); + if (found == std::string::npos) + return NULL; + if (found == (args + 1) || path[found - 1] == '&') { + found += ARRAYSIZE(kTargetPeerIdParam) - 1; + break; + } + args = found + ARRAYSIZE(kTargetPeerIdParam) - 1; + } while (true); + int id = atoi(&path[found]); + Members::const_iterator i = members_.begin(); + for (; i != members_.end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + return NULL; +} + +bool PeerChannel::AddMember(DataSocket* ds) { + RTC_DCHECK(IsPeerConnection(ds)); + ChannelMember* new_guy = new ChannelMember(ds); + Members failures; + BroadcastChangedState(*new_guy, &failures); + HandleDeliveryFailures(&failures); + members_.push_back(new_guy); + + printf("New member added (total=%s): %s\n", + size_t2str(members_.size()).c_str(), new_guy->name().c_str()); + + // Let the newly connected peer know about other members of the channel. + std::string content_type; + std::string response = BuildResponseForNewMember(*new_guy, &content_type); + ds->Send("200 Added", true, content_type, new_guy->GetPeerIdHeader(), + response); + return true; +} + +void PeerChannel::CloseAll() { + Members::const_iterator i = members_.begin(); + for (; i != members_.end(); ++i) { + (*i)->QueueResponse("200 OK", "text/plain", "", "Server shutting down"); + } + DeleteAll(); +} + +void PeerChannel::OnClosing(DataSocket* ds) { + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) { + ChannelMember* m = (*i); + m->OnClosing(ds); + if (!m->connected()) { + i = members_.erase(i); + Members failures; + BroadcastChangedState(*m, &failures); + HandleDeliveryFailures(&failures); + delete m; + if (i == members_.end()) + break; + } + } + printf("Total connected: %s\n", size_t2str(members_.size()).c_str()); +} + +void PeerChannel::CheckForTimeout() { + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) { + ChannelMember* m = (*i); + if (m->TimedOut()) { + printf("Timeout: %s\n", m->name().c_str()); + m->set_disconnected(); + i = members_.erase(i); + Members failures; + BroadcastChangedState(*m, &failures); + HandleDeliveryFailures(&failures); + delete m; + if (i == members_.end()) + break; + } + } +} + +void PeerChannel::DeleteAll() { + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) + delete (*i); + members_.clear(); +} + +void PeerChannel::BroadcastChangedState(const ChannelMember& member, + Members* delivery_failures) { + // This function should be called prior to DataSocket::Close(). + RTC_DCHECK(delivery_failures); + + if (!member.connected()) { + printf("Member disconnected: %s\n", member.name().c_str()); + } + + Members::iterator i = members_.begin(); + for (; i != members_.end(); ++i) { + if (&member != (*i)) { + if (!(*i)->NotifyOfOtherMember(member)) { + (*i)->set_disconnected(); + delivery_failures->push_back(*i); + i = members_.erase(i); + if (i == members_.end()) + break; + } + } + } +} + +void PeerChannel::HandleDeliveryFailures(Members* failures) { + RTC_DCHECK(failures); + + while (!failures->empty()) { + Members::iterator i = failures->begin(); + ChannelMember* member = *i; + RTC_DCHECK(!member->connected()); + failures->erase(i); + BroadcastChangedState(*member, failures); + delete member; + } +} + +// Builds a simple list of "name,id\n" entries for each member. +std::string PeerChannel::BuildResponseForNewMember(const ChannelMember& member, + std::string* content_type) { + RTC_DCHECK(content_type); + + *content_type = "text/plain"; + // The peer itself will always be the first entry. + std::string response(member.GetEntry()); + for (Members::iterator i = members_.begin(); i != members_.end(); ++i) { + if (member.id() != (*i)->id()) { + RTC_DCHECK((*i)->connected()); + response += (*i)->GetEntry(); + } + } + + return response; +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h new file mode 100644 index 0000000000..c3624908ac --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/peer_channel.h @@ -0,0 +1,118 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ + +#include <time.h> + +#include <queue> +#include <string> +#include <vector> + +class DataSocket; + +// Represents a single peer connected to the server. +class ChannelMember { + public: + explicit ChannelMember(DataSocket* socket); + ~ChannelMember(); + + bool connected() const { return connected_; } + int id() const { return id_; } + void set_disconnected() { connected_ = false; } + bool is_wait_request(DataSocket* ds) const; + const std::string& name() const { return name_; } + + bool TimedOut(); + + std::string GetPeerIdHeader() const; + + bool NotifyOfOtherMember(const ChannelMember& other); + + // Returns a string in the form "name,id\n". + std::string GetEntry() const; + + void ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer); + + void OnClosing(DataSocket* ds); + + void QueueResponse(const std::string& status, + const std::string& content_type, + const std::string& extra_headers, + const std::string& data); + + void SetWaitingSocket(DataSocket* ds); + + protected: + struct QueuedResponse { + std::string status, content_type, extra_headers, data; + }; + + DataSocket* waiting_socket_; + int id_; + bool connected_; + time_t timestamp_; + std::string name_; + std::queue<QueuedResponse> queue_; + static int s_member_id_; +}; + +// Manages all currently connected peers. +class PeerChannel { + public: + typedef std::vector<ChannelMember*> Members; + + PeerChannel() {} + + ~PeerChannel() { DeleteAll(); } + + const Members& members() const { return members_; } + + // Returns true if the request should be treated as a new ChannelMember + // request. Otherwise the request is not peerconnection related. + static bool IsPeerConnection(const DataSocket* ds); + + // Finds a connected peer that's associated with the `ds` socket. + ChannelMember* Lookup(DataSocket* ds) const; + + // Checks if the request has a "peer_id" parameter and if so, looks up the + // peer for which the request is targeted at. + ChannelMember* IsTargetedRequest(const DataSocket* ds) const; + + // Adds a new ChannelMember instance to the list of connected peers and + // associates it with the socket. + bool AddMember(DataSocket* ds); + + // Closes all connections and sends a "shutting down" message to all + // connected peers. + void CloseAll(); + + // Called when a socket was determined to be closing by the peer (or if the + // connection went dead). + void OnClosing(DataSocket* ds); + + void CheckForTimeout(); + + protected: + void DeleteAll(); + void BroadcastChangedState(const ChannelMember& member, + Members* delivery_failures); + void HandleDeliveryFailures(Members* failures); + + // Builds a simple list of "name,id\n" entries for each member. + std::string BuildResponseForNewMember(const ChannelMember& member, + std::string* content_type); + + protected: + Members members_; +}; + +#endif // EXAMPLES_PEERCONNECTION_SERVER_PEER_CHANNEL_H_ diff --git a/third_party/libwebrtc/examples/peerconnection/server/server_test.html b/third_party/libwebrtc/examples/peerconnection/server/server_test.html new file mode 100644 index 0000000000..0a165f19d5 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/server_test.html @@ -0,0 +1,237 @@ +<html> +<head> +<title>PeerConnection server test page</title> + +<script> +var request = null; +var hangingGet = null; +var localName; +var server; +var my_id = -1; +var other_peers = {}; +var message_counter = 0; + +function trace(txt) { + var elem = document.getElementById("debug"); + elem.innerHTML += txt + "<br>"; +} + +function handleServerNotification(data) { + trace("Server notification: " + data); + var parsed = data.split(','); + if (parseInt(parsed[2]) != 0) + other_peers[parseInt(parsed[1])] = parsed[0]; +} + +function handlePeerMessage(peer_id, data) { + ++message_counter; + var str = "Message from '" + other_peers[peer_id] + "' "; + str += "<span id='toggle_" + message_counter + "' onclick='toggleMe(this);' "; + str += "style='cursor: pointer'>+</span><br>"; + str += "<blockquote id='msg_" + message_counter + "' style='display:none'>"; + str += data + "</blockquote>"; + trace(str); + if (document.getElementById("loopback").checked) { + if (data.search("offer") != -1) { + // In loopback mode, if DTLS is enabled, notify the client to disable it. + // Otherwise replace the offer with an answer. + if (data.search("fingerprint") != -1) + data = data.replace("offer", "offer-loopback"); + else + data = data.replace("offer", "answer"); + } + sendToPeer(peer_id, data); + } +} + +function GetIntHeader(r, name) { + var val = r.getResponseHeader(name); + return val != null && val.length ? parseInt(val) : -1; +} + +function hangingGetCallback() { + try { + if (hangingGet.readyState != 4) + return; + if (hangingGet.status != 200) { + trace("server error: " + hangingGet.statusText); + disconnect(); + } else { + var peer_id = GetIntHeader(hangingGet, "Pragma"); + if (peer_id == my_id) { + handleServerNotification(hangingGet.responseText); + } else { + handlePeerMessage(peer_id, hangingGet.responseText); + } + } + + if (hangingGet) { + hangingGet.abort(); + hangingGet = null; + } + + if (my_id != -1) + window.setTimeout(startHangingGet, 0); + } catch (e) { + trace("Hanging get error: " + e.description); + } +} + +function startHangingGet() { + try { + hangingGet = new XMLHttpRequest(); + hangingGet.onreadystatechange = hangingGetCallback; + hangingGet.ontimeout = onHangingGetTimeout; + hangingGet.open("GET", server + "/wait?peer_id=" + my_id, true); + hangingGet.send(); + } catch (e) { + trace("error" + e.description); + } +} + +function onHangingGetTimeout() { + trace("hanging get timeout. issuing again."); + hangingGet.abort(); + hangingGet = null; + if (my_id != -1) + window.setTimeout(startHangingGet, 0); +} + +function signInCallback() { + try { + if (request.readyState == 4) { + if (request.status == 200) { + var peers = request.responseText.split("\n"); + my_id = parseInt(peers[0].split(',')[1]); + trace("My id: " + my_id); + for (var i = 1; i < peers.length; ++i) { + if (peers[i].length > 0) { + trace("Peer " + i + ": " + peers[i]); + var parsed = peers[i].split(','); + other_peers[parseInt(parsed[1])] = parsed[0]; + } + } + startHangingGet(); + request = null; + } + } + } catch (e) { + trace("error: " + e.description); + } +} + +function signIn() { + try { + request = new XMLHttpRequest(); + request.onreadystatechange = signInCallback; + request.open("GET", server + "/sign_in?" + localName, true); + request.send(); + } catch (e) { + trace("error: " + e.description); + } +} + +function sendToPeer(peer_id, data) { + if (my_id == -1) { + alert("Not connected"); + return; + } + if (peer_id == my_id) { + alert("Can't send a message to oneself :)"); + return; + } + var r = new XMLHttpRequest(); + r.open("POST", server + "/message?peer_id=" + my_id + "&to=" + peer_id, + false); + r.setRequestHeader("Content-Type", "text/plain"); + r.send(data); + r = null; +} + +function connect() { + localName = document.getElementById("local").value.toLowerCase(); + server = document.getElementById("server").value.toLowerCase(); + if (localName.length == 0) { + alert("I need a name please."); + document.getElementById("local").focus(); + } else { + document.getElementById("connect").disabled = true; + document.getElementById("disconnect").disabled = false; + document.getElementById("send").disabled = false; + signIn(); + } +} + +function disconnect() { + if (request) { + request.abort(); + request = null; + } + + if (hangingGet) { + hangingGet.abort(); + hangingGet = null; + } + + if (my_id != -1) { + request = new XMLHttpRequest(); + request.open("GET", server + "/sign_out?peer_id=" + my_id, false); + request.send(); + request = null; + my_id = -1; + } + + document.getElementById("connect").disabled = false; + document.getElementById("disconnect").disabled = true; + document.getElementById("send").disabled = true; +} + +window.onbeforeunload = disconnect; + +function send() { + var text = document.getElementById("message").value; + var peer_id = parseInt(document.getElementById("peer_id").value); + if (!text.length || peer_id == 0) { + alert("No text supplied or invalid peer id"); + } else { + sendToPeer(peer_id, text); + } +} + +function toggleMe(obj) { + var id = obj.id.replace("toggle", "msg"); + var t = document.getElementById(id); + if (obj.innerText == "+") { + obj.innerText = "-"; + t.style.display = "block"; + } else { + obj.innerText = "+"; + t.style.display = "none"; + } +} + +</script> + +</head> +<body> +Server: <input type="text" id="server" value="http://localhost:8888" /><br> +<input type="checkbox" id="loopback" checked="checked"/> Loopback (just send +received messages right back)<br> +Your name: <input type="text" id="local" value="my_name"/> +<button id="connect" onclick="connect();">Connect</button> +<button disabled="true" id="disconnect" + onclick="disconnect();">Disconnect</button> +<br> +<table><tr><td> +Target peer id: <input type="text" id="peer_id" size="3"/></td><td> +Message: <input type="text" id="message"/></td><td> +<button disabled="true" id="send" onclick="send();">Send</button> +</td></tr></table> +<button onclick="document.getElementById('debug').innerHTML='';"> +Clear log</button> + +<pre id="debug"> +</pre> +<br><hr> +</body> +</html> diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.cc b/third_party/libwebrtc/examples/peerconnection/server/utils.cc new file mode 100644 index 0000000000..5e61e601d9 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/utils.cc @@ -0,0 +1,25 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "examples/peerconnection/server/utils.h" + +#include <stdio.h> + +#include "rtc_base/string_encode.h" + +using rtc::ToString; + +std::string int2str(int i) { + return ToString(i); +} + +std::string size_t2str(size_t i) { + return ToString(i); +} diff --git a/third_party/libwebrtc/examples/peerconnection/server/utils.h b/third_party/libwebrtc/examples/peerconnection/server/utils.h new file mode 100644 index 0000000000..85c04a40e9 --- /dev/null +++ b/third_party/libwebrtc/examples/peerconnection/server/utils.h @@ -0,0 +1,25 @@ +/* + * Copyright 2011 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ +#define EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ + +#include <stddef.h> + +#include <string> + +#ifndef ARRAYSIZE +#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) +#endif + +std::string int2str(int i); +std::string size_t2str(size_t i); + +#endif // EXAMPLES_PEERCONNECTION_SERVER_UTILS_H_ |