using namespace std;
-#include <iostream>
-
namespace Msp {
namespace Http {
Client::Client():
sock(0),
event_disp(0),
+ user_agent("libmsphttp/0.1"),
request(0),
response(0)
{ }
if(request)
throw InvalidState("Already processing a request");
- RefPtr<Net::SockAddr> 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<Net::SockAddr> addr=Net::resolve(host);
delete sock;
sock=new Net::StreamSocket(addr->get_family());
sock->connect(*addr);
request=new Request(r);
+ if(!user_agent.empty())
+ request->set_header("User-Agent", user_agent);
delete response;
response=0;
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);
void Client::wait_response()
{
- while(!response || !response->get_complete())
+ while(request && (!response || !response->is_complete()))
tick();
}
in_buf.erase(0, len);
}
- if(response && response->get_complete())
+ if(response && response->is_complete())
{
signal_response_complete.emit(*response);
private:
Net::StreamSocket *sock;
IO::EventDispatcher *event_disp;
+ std::string user_agent;
Request *request;
Response *response;
std::string in_buf;
~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();
using namespace std;
-#include <iostream>
-
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;
}
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)
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<unsigned>(i->second)-content.size();
return len;
}
- i=headers.find("transfer-encoding");
+ i=headers.find("Transfer-Encoding");
if(i!=headers.end() && strcasecmp(i->second, "chunked")==0)
{
unsigned pos=0;
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
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
return result;
}
+Request Request::parse(const string &str)
+{
+ unsigned lf=str.find('\n');
+ vector<string> 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;
}
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 &);
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<int>(status), status);
result+=str_common();
return result;
Response result;
unsigned lf=str.find('\n');
- vector<string> parts=split(str.substr(0, lf), ' ');
+ vector<string> 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<Status>(lexical_cast<unsigned>(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));
--- /dev/null
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008 Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include <exception>
+#include <msp/core/refptr.h>
+#include <msp/net/inet.h>
+#include <msp/net/streamsocket.h>
+#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<Client>::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<Client>::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<Client>::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> 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<Net::StreamSocket> s):
+ sock(s),
+ request(0),
+ stale(false)
+{ }
+
+Server::Client::~Client()
+{
+ delete request;
+}
+
+} // namespace Http
+} // namespace Msp
--- /dev/null
+/* $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 <msp/core/refptr.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/net/streamlistensocket.h>
+
+namespace Msp {
+namespace Http {
+
+class Request;
+class Response;
+
+class Server
+{
+public:
+ sigc::signal<void, const Request &, Response &> signal_request;
+
+private:
+ struct Client
+ {
+ RefPtr<Net::StreamSocket> sock;
+ std::string in_buf;
+ Request *request;
+ bool stale;
+
+ Client(RefPtr<Net::StreamSocket>);
+ ~Client();
+ };
+
+ Net::StreamListenSocket sock;
+ std::list<Client> 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