From: Mikko Rasa Date: Thu, 25 Dec 2008 08:44:19 +0000 (+0000) Subject: Add Server class X-Git-Url: http://git.tdb.fi/?p=libs%2Fnet.git;a=commitdiff_plain;h=b06cf07513fdd85e249398ebfce500fbf3dfb6a7 Add Server class Get rid of the X-Port header and put port in Host, as per HTTP spec Support for User-Agent header in Client Move header parsing to Message Normalize header names to make each word start with uppercase letter --- diff --git a/source/client.cpp b/source/client.cpp index a4bc342..be57918 100644 --- a/source/client.cpp +++ b/source/client.cpp @@ -15,14 +15,13 @@ Distributed under the LGPL using namespace std; -#include - namespace Msp { namespace Http { Client::Client(): sock(0), event_disp(0), + user_agent("libmsphttp/0.1"), request(0), response(0) { } @@ -48,7 +47,10 @@ void Client::start_request(const Request &r) if(request) throw InvalidState("Already processing a request"); - RefPtr addr=Net::resolve(r.get_header("host")+":"+r.get_header("x-port")); + string host=r.get_header("Host"); + if(host.find(':')==string::npos) + host+=":80"; + RefPtr addr=Net::resolve(host); delete sock; sock=new Net::StreamSocket(addr->get_family()); @@ -62,6 +64,8 @@ void Client::start_request(const Request &r) sock->connect(*addr); request=new Request(r); + if(!user_agent.empty()) + request->set_header("User-Agent", user_agent); delete response; response=0; @@ -83,7 +87,7 @@ void Client::tick() while(IO::PollEvent ev=IO::poll(*sock, sock->get_events(), Time::zero)) sock->event(ev); - if(response && response->get_complete()) + if(response && response->is_complete()) { signal_response_complete.emit(*response); @@ -96,7 +100,7 @@ void Client::tick() void Client::wait_response() { - while(!response || !response->get_complete()) + while(request && (!response || !response->is_complete())) tick(); } @@ -143,7 +147,7 @@ void Client::data_available() in_buf.erase(0, len); } - if(response && response->get_complete()) + if(response && response->is_complete()) { signal_response_complete.emit(*response); diff --git a/source/client.h b/source/client.h index 2554ac4..8c084ce 100644 --- a/source/client.h +++ b/source/client.h @@ -28,6 +28,7 @@ public: private: Net::StreamSocket *sock; IO::EventDispatcher *event_disp; + std::string user_agent; Request *request; Response *response; std::string in_buf; @@ -39,6 +40,7 @@ public: ~Client(); void use_event_dispatcher(IO::EventDispatcher *); + void set_user_agent(const std::string &); void start_request(const Request &); const Response *get_url(const std::string &); void tick(); diff --git a/source/message.cpp b/source/message.cpp index e0a5e7e..93937c9 100644 --- a/source/message.cpp +++ b/source/message.cpp @@ -12,21 +12,25 @@ Distributed under the LGPL using namespace std; -#include - namespace Msp { namespace Http { +Message::Message(): + http_version(0x11), + chunk_length(0), + complete(false) +{ } + void Message::set_header(const string &hdr, const string &val) { - headers[tolower(hdr)]=val; + headers[normalize_header_name(hdr)]=val; } const string &Message::get_header(const string &hdr) const { - HeaderMap::const_iterator i=headers.find(hdr); + HeaderMap::const_iterator i=headers.find(normalize_header_name(hdr)); if(i==headers.end()) - throw KeyError(format("Header %s is not defined", hdr)); + throw KeyError("Undefined header", hdr); return i->second; } @@ -34,9 +38,9 @@ const string &Message::get_header(const string &hdr) const void Message::add_content(const string &d) { content+=d; - if(headers.count("content-type")==0) - set_header("content-type", "text/plain"); - set_header("content-length", lexical_cast(content.size())); + if(headers.count("Content-Type")==0) + set_header("Content-Type", "text/plain"); + set_header("Content-Length", lexical_cast(content.size())); } void Message::set_user_data(const Variant &d) @@ -49,7 +53,7 @@ unsigned Message::parse_content(const string &d) if(complete) return 0; - HeaderMap::const_iterator i=headers.find("content-length"); + HeaderMap::const_iterator i=headers.find("Content-Length"); if(i!=headers.end()) { unsigned needed=lexical_cast(i->second)-content.size(); @@ -63,7 +67,7 @@ unsigned Message::parse_content(const string &d) return len; } - i=headers.find("transfer-encoding"); + i=headers.find("Transfer-Encoding"); if(i!=headers.end() && strcasecmp(i->second, "chunked")==0) { unsigned pos=0; @@ -92,26 +96,62 @@ unsigned Message::parse_content(const string &d) return pos; } + complete=true; return 0; } -Message::Message(): - http_version(0x11), - chunk_length(0), - complete(false) -{ } +unsigned Message::parse_headers(const string &d) +{ + unsigned start=0; + while(1) + { + unsigned lf=d.find('\n', start); + if(lf==string::npos) + throw InvalidParameterValue("Incomplete response"); + if(lf==start || (d[start]=='\r' && lf==start+1)) + return lf+1; + + unsigned colon=d.find(':', start); + if(colon>lf) + throw InvalidParameterValue("No colon in header"); + + set_header(d.substr(start, colon-start), strip(d.substr(colon+1, lf-colon-1))); + + start=lf+1; + } +} string Message::str_common() const { string result; for(HeaderMap::const_iterator i=headers.begin(); i!=headers.end(); ++i) - result+=format("%s: %s\r\n", i->first, i->second); + if(i->first[0]!='-') + result+=format("%s: %s\r\n", i->first, i->second); result+="\r\n"; result+=content; return result; } +string Message::normalize_header_name(const string &hdr) const +{ + string result=hdr; + bool upper=true; + for(string::iterator i=result.begin(); i!=result.end(); ++i) + { + if(upper) + { + *i=toupper(*i); + upper=false; + } + else if(*i=='-') + upper=true; + else + *i=tolower(*i); + } + return result; +} + } // namespace Http } // namespace Msp diff --git a/source/message.h b/source/message.h index 1ccc976..ae38ffa 100644 --- a/source/message.h +++ b/source/message.h @@ -38,11 +38,13 @@ public: const std::string &get_content() const { return content; } void set_user_data(const Variant &); const Variant &get_user_data() const { return user_data; } - bool get_complete() const { return complete; } + bool is_complete() const { return complete; } unsigned parse_content(const std::string &); virtual std::string str() const =0; protected: + unsigned parse_headers(const std::string &); std::string str_common() const; + std::string normalize_header_name(const std::string &) const; }; } // namespace Http diff --git a/source/request.cpp b/source/request.cpp index e63dc66..bcf36c9 100644 --- a/source/request.cpp +++ b/source/request.cpp @@ -28,21 +28,35 @@ string Request::str() const return result; } +Request Request::parse(const string &str) +{ + unsigned lf=str.find('\n'); + vector parts=split(str.substr(0, lf-(str[lf-1]=='\r')), ' ', 2); + if(parts.size()<3) + throw InvalidParameterValue("Invalid request"); + + Request result(parts[0], parts[1]); + result.http_version=parse_version(parts[2]); + + lf+=result.parse_headers(str.substr(lf+1)); + + result.parse_content(str.substr(lf+1)); + + return result; +} + Request Request::from_url(const string &url) { - if(RegMatch match=Regex("^http://([a-zA-Z0-9.-]+)(:([0-9]+))?(/[^ #]*)?$").match(url)) + if(RegMatch match=Regex("^http://([a-zA-Z0-9.-]+(:[0-9]+)?)(/[^ #]*)?$").match(url)) { string host=match[1].str; - string port="80"; - if(match[3]) - port=match[3].str; - string path=match[4].str; + string path=match[3].str; if(path.empty()) path="/"; Request result("GET", path); - result.set_header("host", host); - result.set_header("x-port", port); + result.set_header("Host", host); + result.set_header("Connection", "close"); return result; } diff --git a/source/request.h b/source/request.h index 26d668f..b04fb7e 100644 --- a/source/request.h +++ b/source/request.h @@ -22,6 +22,8 @@ private: public: Request(const std::string &, const std::string &); + const std::string &get_method() const { return method; } + const std::string &get_path() const { return path; } virtual std::string str() const; static Request parse(const std::string &); diff --git a/source/response.cpp b/source/response.cpp index 0defebd..4da6742 100644 --- a/source/response.cpp +++ b/source/response.cpp @@ -20,7 +20,7 @@ Response::Response(Status s): string Response::str() const { - string result=format("%s %d %s\r\n", version_str(http_version), int(status), status); + string result=format("%s %d %s\r\n", version_str(http_version), static_cast(status), status); result+=str_common(); return result; @@ -31,30 +31,14 @@ Response Response::parse(const string &str) Response result; unsigned lf=str.find('\n'); - vector parts=split(str.substr(0, lf), ' '); + vector parts=split(str.substr(0, lf), ' ', 2); if(parts.size()<2) throw InvalidParameterValue("Invalid response"); result.http_version=parse_version(parts[0]); result.status=static_cast(lexical_cast(parts[1])); - unsigned start=lf+1; - while(1) - { - lf=str.find('\n', start); - if(lf==string::npos) - throw InvalidParameterValue("Incomplete response"); - if(lf==start || (str[start]=='\r' && lf==start+1)) - break; - - unsigned colon=str.find(':', start); - if(colon>lf) - throw InvalidParameterValue("No colon in header"); - - result.set_header(str.substr(start, colon-start), strip(str.substr(colon+1, lf-colon-1))); - - start=lf+1; - } + lf+=result.parse_headers(str.substr(lf+1)); result.parse_content(str.substr(lf+1)); diff --git a/source/server.cpp b/source/server.cpp new file mode 100644 index 0000000..7f159ac --- /dev/null +++ b/source/server.cpp @@ -0,0 +1,142 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#include +#include +#include +#include +#include "request.h" +#include "response.h" +#include "server.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Server::Server(unsigned port): + sock(Net::INET), + event_disp(0) +{ + sock.signal_data_available.connect(sigc::mem_fun(this, &Server::data_available)); + sock.listen(Net::InetAddr(0, port), 8); +} + +void Server::use_event_dispatcher(IO::EventDispatcher *ed) +{ + if(event_disp) + { + event_disp->remove(sock); + for(list::iterator i=clients.begin(); i!=clients.end(); ++i) + event_disp->remove(*i->sock); + } + event_disp=ed; + if(event_disp) + { + event_disp->add(sock); + for(list::iterator i=clients.begin(); i!=clients.end(); ++i) + event_disp->add(*i->sock); + } +} + +void Server::data_available() +{ + Net::StreamSocket *csock=sock.accept(); + clients.push_back(Client(csock)); + csock->signal_data_available.connect(sigc::bind(sigc::mem_fun(this, &Server::client_data_available), sigc::ref(clients.back()))); + csock->signal_end_of_file.connect(sigc::bind(sigc::mem_fun(this, &Server::client_end_of_file), sigc::ref(clients.back()))); + if(event_disp) + event_disp->add(*csock); +} + +void Server::client_data_available(Client &cl) +{ + for(list::iterator i=clients.begin(); i!=clients.end(); ++i) + if(i->stale && &*i!=&cl) + { + clients.erase(i); + break; + } + + char rbuf[4096]; + unsigned len=cl.sock->read(rbuf, sizeof(rbuf)); + cl.in_buf.append(rbuf, len); + + RefPtr response; + if(!cl.request) + { + if(cl.in_buf.find("\r\n\r\n")!=string::npos || cl.in_buf.find("\n\n")!=string::npos) + { + try + { + cl.request=new Request(Request::parse(cl.in_buf)); + if(cl.request->get_method()!="GET") + { + response=new Response(NOT_IMPLEMENTED); + response->add_content("Method not implemented"); + } + } + catch(const exception &e) + { + response=new Response(BAD_REQUEST); + response->add_content(e.what()); + } + cl.in_buf=string(); + } + } + else + { + len=cl.request->parse_content(cl.in_buf); + cl.in_buf.erase(0, len); + } + + if(cl.request && cl.request->is_complete() && !response) + { + response=new Response(NONE); + try + { + signal_request.emit(*cl.request, *response); + if(response->get_status()==NONE) + { + response=new Response(NOT_FOUND); + response->add_content("The requested resource was not found"); + } + } + catch(const exception &e) + { + response=new Response(INTERNAL_ERROR); + response->add_content(e.what()); + } + } + + if(response) + { + cl.sock->write(response->str()); + cl.sock->close(); + cl.stale=true; + } +} + +void Server::client_end_of_file(Client &cl) +{ + cl.stale=true; +} + + +Server::Client::Client(RefPtr s): + sock(s), + request(0), + stale(false) +{ } + +Server::Client::~Client() +{ + delete request; +} + +} // namespace Http +} // namespace Msp diff --git a/source/server.h b/source/server.h new file mode 100644 index 0000000..ea12134 --- /dev/null +++ b/source/server.h @@ -0,0 +1,54 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#ifndef MSP_HTTP_SERVER_H_ +#define MSP_HTTP_SERVER_H_ + +#include +#include +#include + +namespace Msp { +namespace Http { + +class Request; +class Response; + +class Server +{ +public: + sigc::signal signal_request; + +private: + struct Client + { + RefPtr sock; + std::string in_buf; + Request *request; + bool stale; + + Client(RefPtr); + ~Client(); + }; + + Net::StreamListenSocket sock; + std::list clients; + IO::EventDispatcher *event_disp; + +public: + Server(unsigned); + void use_event_dispatcher(IO::EventDispatcher *); +private: + void data_available(); + void client_data_available(Client &); + void client_end_of_file(Client &); +}; + +} // namespace Http +} // namespace Msp + +#endif