CLOUD: Add GetClientHandler

That ClientHandler is made for responding GET requests. It calculates
stream's length, it allows to specify response code and headers, it can
be used to transfer any ReadStream.
This commit is contained in:
Alexander Tkachev 2016-06-15 23:54:53 +06:00
parent 99c51380fd
commit 13c54f6685
7 changed files with 212 additions and 9 deletions

View file

@ -61,6 +61,7 @@ endif
ifdef USE_SDL_NET ifdef USE_SDL_NET
MODULE_OBJS += \ MODULE_OBJS += \
networking/sdl_net/client.o \ networking/sdl_net/client.o \
networking/sdl_net/getclienthandler.o \
networking/sdl_net/localwebserver.o networking/sdl_net/localwebserver.o
endif endif

View file

@ -28,9 +28,9 @@
namespace Networking { namespace Networking {
Client::Client(): _state(INVALID), _set(nullptr), _socket(nullptr) {} Client::Client(): _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr) {}
Client::Client(SDLNet_SocketSet set, TCPsocket socket): _state(INVALID), _set(nullptr), _socket(nullptr) { Client::Client(SDLNet_SocketSet set, TCPsocket socket): _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr) {
open(set, socket); open(set, socket);
} }
@ -74,8 +74,7 @@ void Client::checkIfHeadersEnded() {
if (position) _state = READ_HEADERS; if (position) _state = READ_HEADERS;
} }
void Client::checkIfBadRequest() { void Client::checkIfBadRequest() {
if (_state != READING_HEADERS) return;
uint32 headersSize = _headers.size(); uint32 headersSize = _headers.size();
bool bad = false; bool bad = false;
@ -114,6 +113,18 @@ void Client::checkIfBadRequest() {
if (bad) _state = BAD_REQUEST; if (bad) _state = BAD_REQUEST;
} }
void Client::setHandler(ClientHandler *handler) {
if (_handler) delete _handler;
_state = BEING_HANDLED;
_handler = handler;
}
void Client::handle() {
if (_state != BEING_HANDLED) warning("handle() called in a wrong Client's state");
if (!_handler) warning("Client doesn't have handler to be handled by");
if (_handler) _handler->handle(this);
}
void Client::close() { void Client::close() {
if (_set) { if (_set) {
if (_socket) { if (_socket) {
@ -137,4 +148,10 @@ ClientState Client::state() { return _state; }
Common::String Client::headers() { return _headers; } Common::String Client::headers() { return _headers; }
bool Client::socketIsReady() { return SDLNet_SocketReady(_socket); }
int Client::recv(void *data, int maxlen) { return SDLNet_TCP_Recv(_socket, data, maxlen); }
int Client::send(void *data, int len) { return SDLNet_TCP_Send(_socket, data, len); }
} // End of namespace Networking } // End of namespace Networking

View file

@ -35,7 +35,16 @@ enum ClientState {
INVALID, INVALID,
READING_HEADERS, READING_HEADERS,
READ_HEADERS, READ_HEADERS,
BAD_REQUEST BAD_REQUEST,
BEING_HANDLED
};
class Client;
class ClientHandler {
public:
virtual ~ClientHandler() {};
virtual void handle(Client *client) = 0;
}; };
class Client { class Client {
@ -43,6 +52,7 @@ class Client {
SDLNet_SocketSet _set; SDLNet_SocketSet _set;
TCPsocket _socket; TCPsocket _socket;
Common::String _headers; Common::String _headers;
ClientHandler *_handler;
void checkIfHeadersEnded(); void checkIfHeadersEnded();
void checkIfBadRequest(); void checkIfBadRequest();
@ -54,10 +64,23 @@ public:
void open(SDLNet_SocketSet set, TCPsocket socket); void open(SDLNet_SocketSet set, TCPsocket socket);
void readHeaders(); void readHeaders();
void setHandler(ClientHandler *handler);
void handle();
void close(); void close();
ClientState state(); ClientState state();
Common::String headers(); Common::String headers();
/**
* Return SDLNet_SocketReady(_socket).
*
* It's "ready" when it has something
* to read (recv()). You can send()
* when this is false.
*/
bool socketIsReady();
int recv(void *data, int maxlen);
int send(void *data, int len);
}; };
} // End of namespace Networking } // End of namespace Networking

View file

@ -0,0 +1,94 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "backends/networking/sdl_net/getclienthandler.h"
#include "common/textconsole.h"
namespace Networking {
GetClientHandler::GetClientHandler(Common::SeekableReadStream *stream): _responseCode(200), _headersPrepared(false), _stream(stream) {}
GetClientHandler::~GetClientHandler() { delete _stream; }
const char *GetClientHandler::responseMessage(long responseCode) {
switch (responseCode) {
case 200: return "OK";
case 400: return "Bad Request";
case 404: return "Not Found";
case 500: return "Internal Server Error";
}
return "Unknown";
}
void GetClientHandler::prepareHeaders() {
if (!_specialHeaders.contains("Content-Type"))
setHeader("Content-Type", "text/html");
if (!_specialHeaders.contains("Content-Length") && _stream)
setHeader("Content-Length", Common::String::format("%u", _stream->size()));
_headers = Common::String::format("HTTP/1.1 %d %s\r\n", _responseCode, responseMessage(_responseCode));
for (Common::HashMap<Common::String, Common::String>::iterator i = _specialHeaders.begin(); i != _specialHeaders.end(); ++i)
_headers += i->_key + ": " + i->_value + "\r\n";
_headers += "\r\n";
_headersPrepared = true;
}
void GetClientHandler::handle(Client *client) {
if (!client) return;
if (!_headersPrepared) prepareHeaders();
const int kBufSize = 16 * 1024;
char buf[kBufSize];
uint32 readBytes;
// send headers first
if (_headers.size() > 0) {
readBytes = _headers.size();
if (readBytes > kBufSize) readBytes = kBufSize;
memcpy(buf, _headers.c_str(), readBytes);
_headers.erase(0, readBytes);
} else {
if (!_stream) {
client->close();
return;
}
readBytes = _stream->read(buf, kBufSize);
}
if (readBytes != 0)
if (client->send(buf, readBytes) != readBytes) {
warning("GetClientHandler: unable to send all bytes to the client");
client->close();
return;
}
// we're done here!
if (_stream->eos()) client->close();
}
void GetClientHandler::setHeader(Common::String name, Common::String value) { _specialHeaders[name] = value; }
void GetClientHandler::setResponseCode(long code) { _responseCode = code; }
} // End of namespace Networking

View file

@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifndef BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H
#define BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H
#include "backends/networking/sdl_net/client.h"
#include "common/hashmap.h"
#include "common/stream.h"
#include "common/hash-str.h"
namespace Networking {
class GetClientHandler: public ClientHandler {
Common::HashMap<Common::String, Common::String> _specialHeaders;
long _responseCode;
bool _headersPrepared;
Common::String _headers;
Common::SeekableReadStream *_stream;
static const char *responseMessage(long responseCode);
void prepareHeaders();
public:
GetClientHandler(Common::SeekableReadStream *stream);
virtual ~GetClientHandler();
virtual void handle(Client *client);
void setHeader(Common::String name, Common::String value);
void setResponseCode(long code);
};
} // End of namespace Networking
#endif

View file

@ -23,6 +23,8 @@
#define FORBIDDEN_SYMBOL_ALLOW_ALL #define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "backends/networking/sdl_net/localwebserver.h" #include "backends/networking/sdl_net/localwebserver.h"
#include "backends/networking/sdl_net/getclienthandler.h"
#include "common/memstream.h"
#include "common/str.h" #include "common/str.h"
#include "common/system.h" #include "common/system.h"
#include "common/timer.h" #include "common/timer.h"
@ -30,6 +32,7 @@
#include <SDL/SDL_net.h> #include <SDL/SDL_net.h>
namespace Common { namespace Common {
class MemoryReadWriteStream;
DECLARE_SINGLETON(Networking::LocalWebserver); DECLARE_SINGLETON(Networking::LocalWebserver);
@ -126,16 +129,17 @@ void LocalWebserver::handleClient(uint32 i) {
//if PUT, check whether we know a handler for that URL //if PUT, check whether we know a handler for that URL
//if no handler, answer with default BAD REQUEST //if no handler, answer with default BAD REQUEST
warning("headers %s", _client[i].headers().c_str()); warning("headers %s", _client[i].headers().c_str());
_client[i].close(); setClientGetHandler(_client[i], "<html><head><title>ScummVM</title></head><body>Hello, World!</body></html>");
break; break;
case BAD_REQUEST: case BAD_REQUEST:
//TODO: answer with BAD REQUEST setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
_client[i].close(); break;
case BEING_HANDLED:
_client[i].handle();
break; break;
} }
} }
void LocalWebserver::acceptClient() { void LocalWebserver::acceptClient() {
if (!SDLNet_SocketReady(_serverSocket)) return; if (!SDLNet_SocketReady(_serverSocket)) return;
@ -150,4 +154,13 @@ void LocalWebserver::acceptClient() {
_client[_clients++].open(_set, client); _client[_clients++].open(_set, client);
} }
void LocalWebserver::setClientGetHandler(Client &client, Common::String response, long code) {
byte *data = new byte[response.size()];
memcpy(data, response.c_str(), response.size());
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
GetClientHandler *handler = new GetClientHandler(stream);
handler->setResponseCode(code);
client.setHandler(handler);
}
} // End of namespace Networking } // End of namespace Networking

View file

@ -51,6 +51,7 @@ class LocalWebserver : public Common::Singleton<LocalWebserver> {
void handle(); void handle();
void handleClient(uint32 i); void handleClient(uint32 i);
void acceptClient(); void acceptClient();
void setClientGetHandler(Client &client, Common::String response, long code = 200);
public: public:
LocalWebserver(); LocalWebserver();