--- /dev/null
+package "msphttp"
+{
+ description "HTTP client and server library";
+ version "0.1";
+
+ require "mspnet";
+ require "mspstrings";
+
+ library "msphttp"
+ {
+ source "source";
+ install true;
+ install_headers "msp/http";
+ };
+};
--- /dev/null
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008 Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include <msp/core/except.h>
+#include <msp/core/refptr.h>
+#include <msp/net/resolve.h>
+#include <msp/time/units.h>
+#include "client.h"
+#include "request.h"
+#include "response.h"
+
+using namespace std;
+
+#include <iostream>
+
+namespace Msp {
+namespace Http {
+
+Client::Client():
+ sock(0),
+ event_disp(0),
+ request(0),
+ response(0)
+{ }
+
+Client::~Client()
+{
+ delete sock;
+ delete request;
+ delete response;
+}
+
+void Client::use_event_dispatcher(IO::EventDispatcher *ed)
+{
+ event_disp=ed;
+ if(sock)
+ event_disp->add(*sock);
+}
+
+void Client::start_request(const Request &r)
+{
+ if(request)
+ throw InvalidState("Already processing a request");
+
+ RefPtr<Net::SockAddr> addr=Net::resolve(r.get_header("host")+":"+r.get_header("x-port"));
+
+ delete sock;
+ sock=new Net::StreamSocket(addr->get_family());
+ sock->set_block(false);
+
+ sock->signal_data_available.connect(sigc::mem_fun(this, &Client::data_available));
+ sock->signal_connect_finished.connect(sigc::mem_fun(this, &Client::connect_finished));
+ if(event_disp)
+ event_disp->add(*sock);
+
+ sock->connect(*addr);
+
+ request=new Request(r);
+
+ delete response;
+ response=0;
+ in_buf.clear();
+}
+
+void Client::get_url(const std::string &url)
+{
+ start_request(Request::from_url(url));
+ wait_response();
+}
+
+void Client::tick()
+{
+ if(!request)
+ return;
+
+ while(IO::PollEvent ev=IO::poll(*sock, sock->get_events(), Time::zero))
+ sock->event(ev);
+
+ if(response && response->get_complete())
+ {
+ signal_response_complete.emit(*response);
+
+ delete sock;
+ sock=0;
+ delete request;
+ request=0;
+ }
+}
+
+void Client::wait_response()
+{
+ while(!response || !response->get_complete())
+ tick();
+}
+
+void Client::abort()
+{
+ delete sock;
+ sock=0;
+ delete request;
+ request=0;
+}
+
+void Client::connect_finished(int err)
+{
+ if(err)
+ {
+ signal_socket_error.emit(err);
+
+ sock->close();
+ delete request;
+ request=0;
+ }
+ else
+ sock->write(request->str());
+}
+
+void Client::data_available()
+{
+ char rbuf[4096];
+ unsigned len=sock->read(rbuf, sizeof(rbuf));
+ in_buf.append(rbuf, len);
+
+ if(!response)
+ {
+ if(in_buf.find("\r\n\r\n")!=string::npos || in_buf.find("\n\n")!=string::npos)
+ {
+ response=new Response(Response::parse(in_buf));
+ in_buf=string();
+ }
+ }
+ else
+ {
+ len=response->parse_data(in_buf);
+ in_buf.erase(0, len);
+ }
+
+ if(response && response->get_complete())
+ {
+ signal_response_complete.emit(*response);
+
+ sock->close();
+ delete request;
+ request=0;
+ }
+}
+
+} // 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_CLIENT_H_
+#define MSP_HTTP_CLIENT_H_
+
+#include <string>
+#include <sigc++/signal.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/net/streamsocket.h>
+
+namespace Msp {
+namespace Http {
+
+class Request;
+class Response;
+
+class Client
+{
+private:
+ Net::StreamSocket *sock;
+ IO::EventDispatcher *event_disp;
+ Request *request;
+ Response *response;
+ std::string in_buf;
+
+public:
+ sigc::signal<void, const Response &> signal_response_complete;
+ sigc::signal<void, int> signal_socket_error;
+
+private:
+ Client(const Client &);
+ Client &operator=(const Client &);
+public:
+ Client();
+ ~Client();
+
+ void use_event_dispatcher(IO::EventDispatcher *);
+ void start_request(const Request &);
+ void get_url(const std::string &);
+ void tick();
+ void wait_response();
+ void abort();
+ const Request *get_request() const { return request; }
+ const Response *get_response() const { return response; }
+private:
+ void connect_finished(int);
+ void data_available();
+};
+
+} // namespace Http
+} // namespace Msp
+
+#endif
--- /dev/null
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008 Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include <cstdlib>
+#include <msp/strings/formatter.h>
+#include <msp/strings/utils.h>
+#include "message.h"
+
+using namespace std;
+
+#include <iostream>
+
+namespace Msp {
+namespace Http {
+
+void Message::set_header(const string &hdr, const string &val)
+{
+ headers[tolower(hdr)]=val;
+}
+
+const string &Message::get_header(const string &hdr) const
+{
+ HeaderMap::const_iterator i=headers.find(hdr);
+ if(i==headers.end())
+ throw KeyError(format("Header %s is not defined", hdr));
+
+ return i->second;
+}
+
+void Message::add_data(const string &d)
+{
+ data+=d;
+ if(headers.count("content-type")==0)
+ set_header("content-type", "text/plain");
+ set_header("content-length", lexical_cast(data.size()));
+}
+
+unsigned Message::parse_data(const string &d)
+{
+ if(complete)
+ return 0;
+
+ HeaderMap::const_iterator i=headers.find("content-length");
+ if(i!=headers.end())
+ {
+ unsigned needed=lexical_cast<unsigned>(i->second)-data.size();
+ unsigned len=min(needed, d.size());
+
+ data.append(d, 0, len);
+
+ if(len==needed)
+ complete=true;
+
+ return len;
+ }
+
+ i=headers.find("transfer-encoding");
+ if(i!=headers.end() && strcasecmp(i->second, "chunked")==0)
+ {
+ unsigned pos=0;
+ while(!complete && pos<d.size())
+ {
+ if(chunk_length==0)
+ {
+ unsigned lf=d.find('\n', pos);
+ if(lf==string::npos)
+ return pos;
+ // XXX strtoul
+ chunk_length=strtoul(strip(d.substr(pos, lf-pos)).c_str(), 0, 16);
+ if(chunk_length==0)
+ complete=true;
+ pos=lf+1;
+ }
+ else
+ {
+ unsigned len=min(chunk_length, d.size()-pos);
+ data.append(d, pos, len);
+ chunk_length-=len;
+ if((pos=d.find('\n', pos+len))!=string::npos)
+ ++pos;
+ }
+ }
+
+ return pos;
+ }
+
+ return 0;
+}
+
+Message::Message():
+ http_version(0x11),
+ chunk_length(0),
+ complete(false)
+{ }
+
+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);
+ result+="\r\n";
+ result+=data;
+
+ return result;
+}
+
+} // 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_MESSAGE_H_
+#define MSP_HTTP_MESSAGE_H_
+
+#include <string>
+#include "misc.h"
+
+namespace Msp {
+namespace Http {
+
+class Message
+{
+public:
+ virtual ~Message() { }
+
+ void set_header(const std::string &, const std::string &);
+ const std::string &get_header(const std::string &) const;
+ const std::string &get_data() const { return data; }
+ bool get_complete() const { return complete; }
+ void add_data(const std::string &);
+ unsigned parse_data(const std::string &);
+ virtual std::string str() const =0;
+protected:
+ Version http_version;
+ HeaderMap headers;
+ std::string data;
+ unsigned chunk_length;
+ bool complete;
+
+ Message();
+ std::string str_common() const;
+};
+
+} // namespace Http
+} // namespace Msp
+
+#endif
--- /dev/null
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008 Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include <msp/strings/formatter.h>
+#include <msp/strings/lexicalcast.h>
+#include <msp/strings/regex.h>
+#include "misc.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Http {
+
+Version parse_version(const std::string &ver)
+{
+ if(RegMatch match=Regex("^HTTP/([0-9]+).([0-9]+)$").match(ver))
+ return lexical_cast<unsigned>(match[1].str)<<4 | lexical_cast<unsigned>(match[2].str);
+ else
+ throw InvalidParameterValue("Invalid HTTP version");
+}
+
+string version_str(Version ver)
+{
+ return format("HTTP/%u.%u", (ver>>4)&0xF, ver&0xF);
+}
+
+} // 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_MISC_H_
+#define MSP_HTTP_MISC_H_
+
+#include <map>
+#include <string>
+
+namespace Msp {
+namespace Http {
+
+typedef std::map<std::string, std::string> HeaderMap;
+typedef unsigned Version;
+
+Version parse_version(const std::string &);
+std::string version_str(Version);
+
+} // namespace Http
+} // namespace Msp
+
+#endif
--- /dev/null
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008 Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include <msp/strings/formatter.h>
+#include <msp/strings/regex.h>
+#include <msp/strings/utils.h>
+#include "request.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Http {
+
+Request::Request(const string &m, const string &p):
+ method(m),
+ path(p)
+{ }
+
+string Request::str() const
+{
+ string result=format("%s %s %s\r\n", method, path, version_str(http_version));
+ result+=str_common();
+
+ return result;
+}
+
+Request Request::from_url(const string &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;
+ if(path.empty())
+ path="/";
+
+ Request result("GET", path);
+ result.set_header("host", host);
+ result.set_header("x-port", port);
+
+ return result;
+ }
+ else
+ throw InvalidParameterValue("Invalid URL");
+}
+
+} // 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_REQUEST_H_
+#define MSP_HTTP_REQUEST_H_
+
+#include <string>
+#include "message.h"
+
+namespace Msp {
+namespace Http {
+
+class Request: public Message
+{
+public:
+ Request(const std::string &, const std::string &);
+ virtual std::string str() const;
+
+ static Request parse(const std::string &);
+ static Request from_url(const std::string &);
+private:
+ std::string method;
+ std::string path;
+};
+
+} // namespace Http
+} // namespace Msp
+
+#endif
--- /dev/null
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008 Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include <msp/strings/formatter.h>
+#include <msp/strings/utils.h>
+#include "response.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Http {
+
+Response::Response(Status s):
+ status(s)
+{ }
+
+string Response::str() const
+{
+ string result=format("%s %d %s\r\n", version_str(http_version), int(status), status);
+ result+=str_common();
+
+ return result;
+}
+
+Response Response::parse(const string &str)
+{
+ Response result;
+
+ unsigned lf=str.find('\n');
+ vector<string> parts=split(str.substr(0, lf), ' ');
+ 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;
+ }
+
+ result.parse_data(str.substr(lf+1));
+
+ return result;
+}
+
+} // 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_RESPONSE_H_
+#define MSP_HTTO_RESPONSE_H_
+
+#include "message.h"
+#include "status.h"
+
+namespace Msp {
+namespace Http {
+
+class Response: public Message
+{
+public:
+ Response(Status);
+ Status get_status() const { return status; }
+ virtual std::string str() const;
+
+ static Response parse(const std::string &);
+private:
+ Status status;
+
+ Response() { }
+};
+
+} // namespace Http
+} // namespace Msp
+
+#endif
--- /dev/null
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008 Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include "status.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Http {
+
+ostream &operator<<(ostream &out, Status status)
+{
+ switch(status)
+ {
+ case None: out<<"None"; break;
+ case OK: out<<"OK"; break;
+ case BadRequest: out<<"Bad Request"; break;
+ case Forbidden: out<<"Forbidden"; break;
+ case NotFound: out<<"Not Found"; break;
+ case InternalError: out<<"Internal Error"; break;
+ case NotImplemented: out<<"Not Implemented"; break;
+ default: out<<"Unknown Status"; break;
+ }
+
+ return out;
+}
+
+} // 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_HTTPSERVER_STATUS_H_
+#define MSP_HTTPSERVER_STATUS_H_
+
+#include <ostream>
+
+namespace Msp {
+namespace Http {
+
+enum Status
+{
+ None=0,
+ OK=200,
+ BadRequest=400,
+ Forbidden=403,
+ NotFound=404,
+ InternalError=500,
+ NotImplemented=501
+};
+
+extern std::ostream &operator<<(std::ostream &, Status);
+
+} // namespace Http
+} // namespace Msp
+
+#endif