]> git.tdb.fi Git - libs/net.git/commitdiff
Initial revision
authorMikko Rasa <tdb@tdb.fi>
Sun, 16 Nov 2008 12:42:29 +0000 (12:42 +0000)
committerMikko Rasa <tdb@tdb.fi>
Sun, 16 Nov 2008 12:42:29 +0000 (12:42 +0000)
13 files changed:
Build [new file with mode: 0644]
source/client.cpp [new file with mode: 0644]
source/client.h [new file with mode: 0644]
source/message.cpp [new file with mode: 0644]
source/message.h [new file with mode: 0644]
source/misc.cpp [new file with mode: 0644]
source/misc.h [new file with mode: 0644]
source/request.cpp [new file with mode: 0644]
source/request.h [new file with mode: 0644]
source/response.cpp [new file with mode: 0644]
source/response.h [new file with mode: 0644]
source/status.cpp [new file with mode: 0644]
source/status.h [new file with mode: 0644]

diff --git a/Build b/Build
new file mode 100644 (file)
index 0000000..f602417
--- /dev/null
+++ b/Build
@@ -0,0 +1,15 @@
+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";
+       };
+};
diff --git a/source/client.cpp b/source/client.cpp
new file mode 100644 (file)
index 0000000..fde7c8d
--- /dev/null
@@ -0,0 +1,153 @@
+/* $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
diff --git a/source/client.h b/source/client.h
new file mode 100644 (file)
index 0000000..d72cbad
--- /dev/null
@@ -0,0 +1,58 @@
+/* $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
diff --git a/source/message.cpp b/source/message.cpp
new file mode 100644 (file)
index 0000000..a8851fa
--- /dev/null
@@ -0,0 +1,113 @@
+/* $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
diff --git a/source/message.h b/source/message.h
new file mode 100644 (file)
index 0000000..c9dc618
--- /dev/null
@@ -0,0 +1,43 @@
+/* $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
diff --git a/source/misc.cpp b/source/misc.cpp
new file mode 100644 (file)
index 0000000..6ac7c5d
--- /dev/null
@@ -0,0 +1,32 @@
+/* $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
diff --git a/source/misc.h b/source/misc.h
new file mode 100644 (file)
index 0000000..8e7ed79
--- /dev/null
@@ -0,0 +1,26 @@
+/* $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
diff --git a/source/request.cpp b/source/request.cpp
new file mode 100644 (file)
index 0000000..e63dc66
--- /dev/null
@@ -0,0 +1,54 @@
+/* $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
diff --git a/source/request.h b/source/request.h
new file mode 100644 (file)
index 0000000..6f713ab
--- /dev/null
@@ -0,0 +1,33 @@
+/* $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
diff --git a/source/response.cpp b/source/response.cpp
new file mode 100644 (file)
index 0000000..86d06ba
--- /dev/null
@@ -0,0 +1,65 @@
+/* $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
diff --git a/source/response.h b/source/response.h
new file mode 100644 (file)
index 0000000..20e144e
--- /dev/null
@@ -0,0 +1,34 @@
+/* $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
diff --git a/source/status.cpp b/source/status.cpp
new file mode 100644 (file)
index 0000000..0eb25f3
--- /dev/null
@@ -0,0 +1,33 @@
+/* $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
diff --git a/source/status.h b/source/status.h
new file mode 100644 (file)
index 0000000..5d2b196
--- /dev/null
@@ -0,0 +1,32 @@
+/* $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