blob: fd5805e1199eed5970ff84af5fb8d912e652c4ba [file] [log] [blame]
Austin Schuh24adb6b2015-09-06 17:37:40 -07001// Copyright (c) 2013, Matt Godbolt
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// Redistributions of source code must retain the above copyright notice, this
8// list of conditions and the following disclaimer.
9//
10// Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13//
14// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24// POSSIBILITY OF SUCH DAMAGE.
25
26// An extraordinarily simple test which presents a web page with some buttons.
27// Clicking on the numbered button increments the number, which is visible to
28// other connected clients. WebSockets are used to do this: by the rather
29// suspicious means of sending raw JavaScript commands to be executed on other
30// clients.
31
32// Same as ws_test, but uses the poll() method and a separate epoll set to
33// demonstrate how Seasocks can be used with another polling system.
34
35#include "seasocks/PrintfLogger.h"
36#include "seasocks/Server.h"
37#include "seasocks/StringUtil.h"
38#include "seasocks/WebSocket.h"
39#include "seasocks/util/Json.h"
40
41#include <cstring>
42#include <iostream>
43#include <memory>
44#include <set>
45#include <sstream>
46#include <string>
47#include <fcntl.h>
48#include <unistd.h>
49#include <sys/epoll.h>
50
51using namespace seasocks;
52using namespace std;
53
54class MyHandler: public WebSocket::Handler {
55public:
56 MyHandler(Server* server) : _server(server), _currentValue(0) {
57 setValue(1);
58 }
59
60 virtual void onConnect(WebSocket* connection) {
61 _connections.insert(connection);
62 connection->send(_currentSetValue.c_str());
63 cout << "Connected: " << connection->getRequestUri()
64 << " : " << formatAddress(connection->getRemoteAddress())
65 << endl;
66 cout << "Credentials: " << *(connection->credentials()) << endl;
67 }
68
69 virtual void onData(WebSocket* connection, const char* data) {
70 if (0 == strcmp("die", data)) {
71 _server->terminate();
72 return;
73 }
74 if (0 == strcmp("close", data)) {
75 cout << "Closing.." << endl;
76 connection->close();
77 cout << "Closed." << endl;
78 return;
79 }
80
81 int value = atoi(data) + 1;
82 if (value > _currentValue) {
83 setValue(value);
84 for (auto connection : _connections) {
85 connection->send(_currentSetValue.c_str());
86 }
87 }
88 }
89
90 virtual void onDisconnect(WebSocket* connection) {
91 _connections.erase(connection);
92 cout << "Disconnected: " << connection->getRequestUri()
93 << " : " << formatAddress(connection->getRemoteAddress())
94 << endl;
95 }
96
97private:
98 set<WebSocket*> _connections;
99 Server* _server;
100 int _currentValue;
101 string _currentSetValue;
102
103 void setValue(int value) {
104 _currentValue = value;
105 _currentSetValue = makeExecString("set", _currentValue);
106 }
107};
108
109int main(int argc, const char* argv[]) {
110 shared_ptr<Logger> logger(new PrintfLogger(Logger::DEBUG));
111
112 Server server(logger);
113
114 shared_ptr<MyHandler> handler(new MyHandler(&server));
115 server.addWebSocketHandler("/ws", handler);
116 server.setStaticPath("src/ws_test_web");
117 if (!server.startListening(9090)) {
118 cerr << "couldn't start listening" << endl;
119 return 1;
120 }
121 int myEpoll = epoll_create(10);
122 epoll_event wakeSeasocks = { EPOLLIN|EPOLLOUT|EPOLLERR, { &server } };
123 epoll_ctl(myEpoll, EPOLL_CTL_ADD, server.fd(), &wakeSeasocks);
124
125 // Also poll stdin
126 epoll_event wakeStdin = { EPOLLIN, { nullptr } };
127 epoll_ctl(myEpoll, EPOLL_CTL_ADD, STDIN_FILENO, &wakeStdin);
128 auto prevFlags = fcntl(STDIN_FILENO, F_GETFL, 0);
129 fcntl(STDIN_FILENO, F_SETFL, prevFlags | O_NONBLOCK);
130
131 cout << "Will echo anything typed in stdin: " << flush;
132 while (true) {
133 constexpr auto maxEvents = 2;
134 epoll_event events[maxEvents];
135 auto res = epoll_wait(myEpoll, events, maxEvents, -1);
136 if (res < 0) {
137 cerr << "epoll returned an error" << endl;
138 return 1;
139 }
140 for (auto i = 0; i < res; ++i) {
141 if (events[i].data.ptr == &server) {
142 auto seasocksResult = server.poll(0);
143 if (seasocksResult == Server::PollResult::Terminated) return 0;
144 if (seasocksResult == Server::PollResult::Error) return 1;
145 } else if (events[i].data.ptr == nullptr) {
146 // Echo stdin to stdout to show we can read from that too.
147 for (;;) {
148 char buf[1024];
149 auto numRead = ::read(STDIN_FILENO, buf, sizeof(buf));
150 if (numRead < 0) {
151 if (errno != EWOULDBLOCK && errno != EAGAIN) {
152 cerr << "Error reading stdin" << endl;
153 return 1;
154 }
155 break;
156 } else if (numRead > 0) {
157 auto written = write(STDOUT_FILENO, buf, numRead);
158 if (written != numRead) {
159 cerr << "Truncated write" << endl;
160 }
161 } else if (numRead == 0) {
162 cerr << "EOF on stdin" << endl;
163 return 0;
164 }
165 }
166 }
167 }
168 }
169 return 0;
170}